程序背景
程序是Java編寫,基於Netty框架寫的客戶端及服務端。
現象
客戶端大數據量持續發UDP數據,作為UDP服務器出現了部分數據頻繁丟失觸發程序自身重傳邏輯。
通過GC日志對比發現丟包的時間點偶有處於Full GC,說明Java程序接收間歇性stop world的不是根因。
觀察Udp的dump
通過watch -n 1 -d 'cat /proc/net/udp >> /usr/udpDump.txt'在發送數據的過程中持續觀察Udp緩沖區的狀況
- /proc/net/udp是瞬時的Udp socket dump,另有/proc/net/udp6用於監控IPv6
- dump輸出里的tx_queue是發送緩沖區,rx_queue是接收緩沖區,單位都是byte
- 如果應用層收發效率足夠好,正常情況下tx_queue和rx_queue兩者永遠是0
- 發送數據過程中頻現rx_queue>0,說明Udp緩沖區有堆積現象
- 輸出解釋見How to monitor Linux UDP buffer available space?、Meaning of fields in /proc/net/udp
觀察Udp的stats
通過watch -n 1 -d 'netstat -su >> /usr/udpStats.txt'持續觀察Udp的stats輸出
- 輸出里packets received的值指應用層從讀入緩沖區里取走的包
- 輸出里packets to unknown port received的值指端口無應用監聽而分發至該端口的包
- 輸出里packet receive errors的值指Udp接收錯誤數,正常情況下應該是0,在觀察中不停增加,證明出現Udp包溢出接收緩沖區的情況
- 發生錯誤的包數與接收錯誤數非一一對應
- 資料參見Udp Packet Receive Errors、Udp packet drops and packet receive error difference
解決問題
服務端代碼優化
定論:
默認的UDP socket讀緩沖區不夠引發系統丟棄UDP包。
服務端代碼優化設置UDP socket讀緩沖區為2M,代碼如下
Bootstrap selfBootStrap = new Bootstrap();
selfBootStrap.group(group);
selfBootStrap.channel(NioDatagramChannel.class);
selfBootStrap.option(ChannelOption.SO_BROADCAST, true);
// 這一行設置了UDP socket讀緩沖區為2M
selfBootStrap.option(ChannelOption.SO_RCVBUF, 1024 * 2048);
selfBootStrap.handler(channelInitializer);
selfBootStrap.localAddress(selfPort);
理論上Udp socket讀緩沖區設置為2M在我們的測試場景下已經足夠。優化后雖有改善但仍有丟包現象。
Linux系統級調優
定論:
應用層設置了UDP socket緩沖區不一定在Linux上生效,原因在於Linux對Udp socket緩沖區存有系統級限制,超過該限制的緩沖區大小無效。
Windows對socket的緩沖區沒有限制
要點分析:
Linux通過net.core.rmem_max控制Udp的讀緩沖區,通過net.core.wmem_max控制Udp的寫緩沖區。
在程序的啟動sh腳本里添加如下代碼修改net.core.rmem_max
# 服務器默認UDP讀緩沖區最大128K。修改為2G。解決UDP丟包問題
rmemCount=`cat /etc/sysctl.conf|grep "net.core.rmem_max" | wc -l`
if [ ${rmemCount} -eq 0 ]
then
echo "net.core.rmem_max = 2147483647" >> /etc/sysctl.conf
sysctl -p
fi
腳本的作用就是修改/etc/sysctl.conf文件,並鍵入sysctl -p命令使自定義參數生效。
資料參見Improving UDP Performance by Configuring OS UDP Buffer Limits、UDP Drops on Linux
