轉自: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