转自:https://blog.csdn.net/xingzheouc/article/details/49946191css
用户数据报协议(英语:User Datagram Protocol,缩写为 UDP),又称使用者资料包协定,是一个简单的面向数据报的传输层协议,正式规范为RFC 768html
在TCP/IP模型中,UDP为网络层以上和应用层如下提供了一个简单的接口。UDP只提供数据的不可靠传递,它一旦把应用程序发给网络层的数据发送出去,就不保留数据备份(因此UDP有时候也被认为是不可靠的数据报协议)。UDP在IP数据报的头部仅仅加入了复用和数据校验(字段)。算法
因为UDP协议只提供数据的不可靠传输,数据包发出去后就无论了,数据包在网络的传输过程当中均可能丢失。甚至即便数据包成功到达了接收端节点,也不意味着应用程序可以收到,由于要从网卡到达应用程序还须要经历不少阶段,每一个阶段均可能丢包。服务器
上图描述了一种应用程序接受网络数据包的典型路径图。网络
首先,NIC(网卡)接收和处理网络数据包。网卡有本身的硬件数据缓冲区,当网络数据流量太大,大到超过网卡的接收数据硬件缓冲区,这时新进入的数据包将覆盖以前缓冲区的数据包,致使丢失。网卡是否丢包取决于网卡自己的计算性能和硬件缓冲区的大小。异步
其次,网卡处理后把数据包交给操做系统缓冲区。数据包在操做系统阶段丢包主要取决于如下因素:socket
最后,当数据包到达应用程序的socket缓冲区,若是应用程序不能及时从socket缓冲区把数据包取走,累积的数据包将会超出应用程序socket缓冲区阀值,致使缓冲区溢出,数据包丢失。数据包在应用程序阶段丢包主要取决于如下因素:ionic
n 网卡缓冲区溢出诊断工具
在Linux操做系统中,能够经过netstat -i –udp <NIC> 命令来诊断网卡缓冲区是否溢出,RX-DRP列显示网卡丢失的数据包个数。性能
For example: netstat -i –udp eth1
[root@TENCENT64 /usr/local/games/udpserver]# netstat -i –udp eth1
Kernel Interface table
Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth1 1500 0 1295218256 0 3 0 7598497 0 0 0 BMRU
上图的输出显示3个数据包被网卡丢掉
能够经过增大网卡缓冲区来有效减小网卡缓冲区溢出。
n 操做系统内核网络缓冲区溢出诊断
在Linux操做系统中能够经过cat /proc/net/snmp | grep -w Udp命令来查看,InErrors 列显示当操做系统UDP队列溢出时丢失的UDP数据包总个数。
[root@TENCENT64 /usr/local/games/udpserver]# cat /proc/net/snmp | grep -w Udp
Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors
Udp: 859428331 12609927 166563611 151449 166563611 0
1) 增大操做系统内核网络缓冲区的大小
2) 在数据包路径图中直接绕过操做系统内核缓冲区,经过使用用户空间栈或一些能够 绕过内核缓冲区的中间件 (e.g. Solarflare's OpenOnload).
3) 关闭未使用的网络相关的应用和服务使操做系统的负载降到最低
4) 系统中仅保留适当数量的工做的网卡,最大效率的合理化利用网卡和系统资源
n 应用程序socket缓冲区溢出诊断
在Linux操做系统中能够经过cat /proc/net/snmp | grep -w Udp命令来查看,RcvbufErrors 列显示当应用程序socket缓冲区溢出时丢失的UDP数据包总个数。
[root@TENCENT64 /usr/local/games/udpserver]# cat /proc/net/snmp | grep -w Udp
Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors
Udp: 859428331 12609927 166563611 151449 166563611 0
有以下几种方法能够有效减缓应用程序socket缓冲区溢出:
1) 接受缓冲区尽量快地处理接受到的数据包(e.g.经过使用NIO的方式来异步非阻塞接受UDP数据包或者提升接受UDP数据包线程的优先级)
2) 增大应用程序接受socket缓冲区大小,注意这个受限于全局socket缓冲区大小,若是应用程序的socket缓冲区大于全局socket缓冲区将没有效果。
3) 把应用程序或接受线程指定到CPU专用的核上
4) 提升应用程序的io优先级(e.g.使用nice或ionice命令来调节)
5) 关闭全部未使用的网络相关的应用和服务使操做系统的负载降到最低
n 网卡缓冲区调优
Linux下运行ethtool -g <NIC>
命令查询网卡的缓冲设置,以下:
[root@TENCENT64 /usr/local/games/udpserver]# ethtool -g eth1
Ring parameters for eth1:
Pre-set maximums:
RX: 4096
RX Mini: 0
RX Jumbo: 0
TX: 4096
Current hardware settings:
RX: 256
RX Mini: 0
RX Jumbo: 0
TX: 256
经过命令ethtool -G d<NIC> rx NEW-BUFFER-SIZE能够设置RX ring的缓冲区大小,改变会当即生效不须要重启操做系统或刷新网络栈,这种变化直接做用于网卡自己而不影响操做系统,不影响操做系统内核网络栈可是会影响网卡固件参数。更大的ring size能承受较大的数据流量而不会丢包,可是由于工做集的增长可能会下降网卡效率,影响性能,因此建议谨慎设置网卡固件参数。
n 操做系统内核缓冲区调优
运行命令sysctl -A | grep net | grep 'mem\|backlog' | grep 'udp_mem\|rmem_max\|max_backlog'查看当前操做系统缓冲区的设置。以下:
[root@TENCENT64 /usr/local/games]# sysctl -A | grep net | grep 'mem\|backlog' | grep 'udp_mem\|rmem_max\|max_backlog'net.core.netdev_max_backlog = 1000net.core.rmem_max = 212992net.ipv4.udp_mem = 188169 250892 376338
增长最大socket接收缓冲区大小为32MB:
sysctl -w net.core.rmem_max=33554432
增长最大可分配的缓冲区空间总量,数值以页面为单位,每一个页面单位等于4096 bytes:
sysctl -w net.ipv4.udp_mem="262144 327680 393216"
增长接收数据包队列大小:
sysctl -w net.core.netdev_max_backlog=2000
修改完成后,须要运行命令 sysctl –p
使之生效
n 应用程序调优
要减小数据包丢失,应用程序必须尽量快从缓冲区取走数据,能够经过适当增大socket缓冲区和采用异步非阻塞的IO来快速从缓冲区取数据,测试采用JAVA NIO构建一个Asynchronous UDP server。
//创建 DatagramChannel dc = DatagramChannel.open(); dc.configureBlocking(false); //本地绑定端口 SocketAddress address = new InetSocketAddress(port); DatagramSocket ds = dc.socket(); ds.setReceiveBufferSize(1024 * 1024 * 32);//设置接收缓冲区大小为32M ds.bind(address); //注册 Selector select = Selector.open(); dc.register(select, SelectionKey.OP_READ); ByteBuffer buffer = ByteBuffer.allocateDirect(1024); System.out.println("Listening on port " + port); while (true) { int num = select.select(); if (num == 0) { continue; } //获得选择键列表 Set Keys = select.selectedKeys(); Iterator it = Keys.iterator(); while (it.hasNext()) { SelectionKey k = (SelectionKey) it.next(); if ((k.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) { DatagramChannel cc = (DatagramChannel) k.channel(); //非阻塞 cc.configureBlocking(false);
UDP发送端增长流量控制,控制每秒发送的数据包,尽可能避免因为发送端发包速率过快,致使UDP接收端缓冲区很快被填满从而出现溢出丢包。测试采用google提供的工具包guava。RateLimiter类来作流控,采用了一种令牌桶的流控算法,RateLimiter会按照必定的频率往桶里扔令牌,线程拿到令牌才能执行,好比你但愿本身的应用程序QPS不要超过1000,那么RateLimiter设置1000的速率后,就会每秒往桶里扔1000个令牌。
采用流控后每秒发指定数量的数据包,并且每秒都会出现波谷波峰,若是不作流控,UDP发送端会全力发包一直在波峰附近抖动,大流量会一直持续,随着时间的增长,UDP发送端生产的速率确定会超过UDP接收端消费的速率,丢包是早晚的。
n 机器类型
发送端和接收端均采用C1类型机器,配置以下:
Intel(R) Xeon(R) CPU X3440 @ 2.53GHz:8
|
8G
|
500G:7200RPM:1:SATA
|
NORAID
|
接收端网卡信息以下:
[root@TENCENT64 /usr/local/games]# ethtool eth1 Settings for eth1: Supported ports: [ TP ] Supported link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Supports auto-negotiation: Yes Advertised link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Advertised pause frame use: Symmetric Advertised auto-negotiation: Yes Speed: 1000Mb/s Duplex: Full Port: Twisted Pair PHYAD: 1 Transceiver: internal Auto-negotiation: on MDI-X: on Supports Wake-on: pumbg Wake-on: g Current message level: 0x00000007 (7) Link detected: yes[root@TENCENT64 /usr/local/games]# ethtool -g eth1Ring parameters for eth1:Pre-set maximums:RX: 4096RX Mini: 0RX Jumbo: 0TX: 4096Current hardware settings:RX: 256RX Mini: 0RX Jumbo: 0TX: 256
n 实际调优
接收端服务器调优后的参数以下:
[root@TENCENT64 /usr/local/games]# sysctl -A | grep net | grep 'mem\|backlog' | grep 'udp_mem\|rmem_max\|max_backlog'net.core.rmem_max = 67108864net.core.netdev_max_backlog = 20000net.ipv4.udp_mem = 754848 1006464 1509696
发送端是否作发送流量控制在测试场景中体现
n 测试场景
场景1:发送100w+数据包,每一个数据包大小512byte,数据包都包含当前的时间戳,不限流,全速发送。发送5次,测试结果以下:
测试客户端:
发100w个512字节的udp包,发100w数据包耗时4.625s,21wQPS
测试服务器端:
客户端发5次包,每次发包100w(每一个包512字节),第一次服务端接受90w丢约10w,第二次服务端接受100w不丢,第三次接受100w不丢,第四次接受97w丢3w,第五次接受100w不丢
服务端记录日志:
服务端操做系统接收UDP记录状况:(和日志记录结果彻底一致)
场景2:发送端增长流量控制,每秒4w数据包,每一个数据包512byte,包含当前时间戳,发送时间持续2小时,测试结果以下:
1.Udpclient端,加入流量控制:
QPS:4W
datapacket:512byte,包含发送的时间戳
持续发送时长:2h
累计发包数: 287920000(2.8792亿)
CPU平均消耗: 16% (8cpu)
内存平均消耗: 0.3%(8G)
2.Udpserver端:
Server端接受前网卡记录的UDP 详情:
[root@TENCENT64 ~]# cat /proc/net/snmp | grep -w UdpUdp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrorsUdp: 1156064488 753197150 918758960 1718431901 918758960 0
Server端接受完全部的udp数据包后网卡记录的UDP详情:
[root@TENCENT64 ~]# cat /proc/net/snmp | grep -w UdpUdp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrorsUdp: 1443984568 753197150 918758960 1718432045 918758960 0
先后变化分析:
InDatagrams: (1443984568-1156064488)= 287920080
InErrors:0 (记录操做系统层面udp丢包,丢包多是由于系统udp队列满了)
RcvbufErrors:0(记录应用程序层面udp丢包),丢包多是由于应用程序socket buffer满了)
Server端日志状况:
总记录日志文件:276个,总大小:138G
日志总数: 287920000 (和udpclient发送数据包总量一致,没有丢包)
根据日志时间戳,简单计算处理能力:
time cost:(1445410477654-1445403277874)/1000=7199.78s
process speed: 287920000/7199.78 = 3.999w/s
CPU消耗: 平均46% (8cpu),要不停异步写日志,IO操做频繁,消耗比较多cpu资源
内存消耗: 平均4.7%(8G)
场景3:发送端增长流量控制,每秒6w数据包,每一个数据包512byte,包含当前时间戳,发送时间持续2小时,出现丢包,测试结果以下:
1.Udpclient端,加入流量控制:
QPS:6W
datapacket:512byte,包含发送的时间戳
持续发送时长:2h
累计发包数: 432000000 (4.32亿)
CPU平均消耗: 70% (8cpu)
内存平均消耗: 0.3%(8G)
2.Udpserver端:
Server端接受前网卡记录的UDP 详情:
[root@TENCENT64 ~]# cat /proc/net/snmp | grep -w UdpUdp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrorsUdp: 2235178200 753197150 918960131 1720242603 918960131 0
Server端接受完全部的udp数据包后网卡记录的UDP详情:
[root@TENCENT64 ~]# cat /proc/net/snmp | grep -w UdpUdp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrorsUdp: 2667158153 753197150 918980378 1720242963 918980378 0
先后变化分析:
InDatagrams: (2667158153 -2235178200)= 431979953
InErrors: (918980378 -918960131)= 20247 (记录操做系统层面udp丢包,丢包多是由于系统udp队列满了)
RcvbufErrors: (918980378 -918960131)= 20247 (记录应用程序层面udp丢包),丢包多是由于应用程序socket buffer满了)
Server端日志状况:
总记录日志文件:413个,总大小:207G
日志总数: 431979753 (和网卡收到udp包总数一致,写日志文件没有丢包)
丢包状况:
Client端发送:432000000,
服务端网卡接受udp包总数:431979953,
日志记录:431979953,
udp网卡接受丢包:20247,
丢包率:1/20000
因为测试服务器硬盘资源有限,只测试了2个小时,随着发送和接受时间增加,丢包率可能会增大。
对比图:不加流控和加流控(限流4w)发送100w个512byte数据包,每毫秒发送数据包雷达波型对比图,雷达波型图中,外围波型值为发送数据包的毫秒值,雷达轴距为每毫秒发送的数据包数取值范围。按顺序,图1为限流4w生成的图,图2为不限流生成的图。从图中能够看出限流时每秒都会出现波谷波峰,不会一直持续高流量发送,能适当缓解UDP接收端的压力;不限流时数据在波峰附近波动,持续高流量发送,对UDP接收端有不少压力,接收端如没及时从缓冲区取走数据或消费能力低于发送端的生成能力,则很容易丢包。
----------------------------------------------------------------------------------------------
总结:UDP发包在不作流控的前提下,发送端很快到达一个相对稳定的波峰值并一直持续发送,接收端网卡或操做系统缓冲区始终有限,随着发包时间不断增长,到某个时间点一定填满接收端网卡和系统的缓冲区,并且发送端的生产速率将远远超过接收端消费速率,必然致使丢包。发送端作了流量控制后,发送速率获得有效控制,不会一直持续高流量发送,每秒都会出现波谷波峰,有效缓解了接收端的压力,在合理发包速率的前提下,经过相关系统调优,基本能够保证不丢包,但要确保数据的高完整性,因为UDP协议的天生不可靠性,仍是要在UDP协议基础上作相关扩展,增长数据完整性校验,方能确保业务数据的完整。
【注】文章第2和第3部分翻译国外一篇文章,原文以下:
http://ref.onixs.biz/lost-multicast-packets-troubleshooting.html