最近工作中遇到某個伺服器應用程式 UDP 丢包,在排查過程中查閱了很多資料,總結出來這篇文章,供更多人參考。
在開始之前,我們先用一張圖解釋 linux 系統接收網絡封包的過程。
● 首先網絡封包通過實體網線發送到網卡
● 網絡驅動程式會把網絡中的封包讀出來放到 ring buffer 中,這個過程使用 DMA(Direct Memory Access),不需要 CPU 參與
● 核心從 ring buffer 中讀取封包進行處理,執行 IP 和 TCP/UDP 層的邏輯,最後把封包放到應用程式的 socket buffer 中
● 應用程式從 socket buffer 中讀取封包進行處理
在接收 UDP 封包的過程中,圖中任何一個過程都可能會主動或者被動地把封包丢棄,是以丢包可能發生在網卡和驅動,也可能發生在系統和應用。
之是以沒有分析發送資料流程,一是因為發送流程和接收類似,隻是方向相反;另外發送流程封包丢失的機率比接收小,隻有在應用程式發送的封包速率大于核心和網卡處理速率時才會發生。
本篇文章假定機器隻有一個名字為 eth0 的 interface,如果有多個 interface 或者 interface 的名字不是 eth0,請按照實際情況進行分析。
NOTE:文中出現的
RX
(receive) 表示接收封包, TX
(transmit) 表示發送封包。
确認有 UDP 丢包發生
要檢視網卡是否有丢包,可以使用
ethtool -S eth0
檢視,在輸出中查找 bad
或者 drop
對應的字段是否有資料,在正常情況下,這些字段對應的數字應該都是 0。如果看到對應的數字在不斷增長,就說明網卡有丢包。
另外一個檢視網卡丢包資料的指令是
ifconfig
,它的輸出中會有 RX
(receive 接收封包)和 TX
(transmit 發送封包)的統計資料: 此外,linux 系統也提供了各個網絡協定的丢包資訊,可以使用 netstat -s
指令檢視,加上 --udp
可以隻看 UDP 相關的封包資料: 對于上面的輸出,關注下面的資訊來檢視 UDP 丢包的情況:
● packet receive errors 不為空,并且在一直增長說明系統有 UDP 丢包
● packets to unknown port received 表示系統接收到的 UDP 封包所在的目标端口沒有應用在監聽,一般是服務沒有啟動導緻的,并不會造成嚴重的問題
● receive buffer errors 表示因為 UDP 的接收緩存太小導緻丢包的數量
NOTE: 并不是丢包數量不為零就有問題,對于 UDP 來說,如果有少量的丢包很可能是預期的行為,比如丢包率(丢包數量/接收封包數量)在萬分之一甚至更低。
網卡或者驅動丢包
之前講過,如果
ethtool -S eth0
中有 rx_***_errors
那麼很可能是網卡有問題,導緻系統丢包,需要聯系伺服器或者網卡供應商進行處理。 netstat -i
也會提供每個網卡的接發封包以及丢包的情況,正常情況下輸出中 error 或者 drop 應該為 0。
如果硬體或者驅動沒有問題,一般網卡丢包是因為設定的緩存區(ring buffer)太小,可以使用
ethtool
指令檢視和設定網卡的 ring buffer。 ethtool -g
可以檢視某個網卡的 ring buffer,比如下面的例子 Pre-set 表示網卡最大的 ring buffer 值,可以使用 ethtool -G eth0 rx 8192
設定它的值。
Linux 系統丢包
linux 系統丢包的原因很多,常見的有:UDP 封包錯誤、防火牆、UDP buffer size 不足、系統負載過高等,這裡對這些丢包原因進行分析。
UDP 封包錯誤
如果在傳輸過程中UDP 封包被修改,會導緻 checksum 錯誤,或者長度錯誤,linux 在接收到 UDP 封包時會對此進行校驗,一旦發明錯誤會把封包丢棄。
如果希望 UDP 封包 checksum 及時有錯也要發送給應用程式,可以在通過 socket 參數禁用 UDP checksum 檢查:
防火牆
如果系統防火牆丢包,表現的行為一般是所有的 UDP 封包都無法正常接收,當然不排除防火牆隻 drop 一部分封包的可能性。
如果遇到丢包比率非常大的情況,請先檢查防火牆規則,保證防火牆沒有主動 drop UDP 封包。
UDP buffer size 不足
linux 系統在接收封包之後,會把封包儲存到緩存區中。因為緩存區的大小是有限的,如果出現 UDP 封包過大(超過緩存區大小或者 MTU 大小)、接收到封包的速率太快,都可能導緻 linux 因為緩存滿而直接丢包的情況。
在系統層面,linux 設定了 receive buffer 可以配置的最大值,可以在下面的檔案中檢視,一般是 linux 在啟動的時候會根據記憶體大小設定一個初始值。
● /proc/sys/net/core/rmem_max:允許設定的 receive buffer 最大值
● /proc/sys/net/core/rmem_default:預設使用的 receive buffer 值
● /proc/sys/net/core/wmem_max:允許設定的 send buffer 最大值
● /proc/sys/net/core/wmem_dafault:預設使用的 send buffer 最大值
但是這些初始值并不是為了應對大流量的 UDP 封包,如果應用程式接收和發送 UDP 封包非常多,需要講這個值調大。可以使用 sysctl 指令讓它立即生效:
sysctl -w net.core.rmem_max=26214400 # 設定為 25M
也可以修改 /etc/sysctl.conf
中對應的參數在下次啟動時讓參數保持生效。
如果封包封包過大,可以在發送方對資料進行分割,保證每個封包的大小在 MTU 内。
另外一個可以配置的參數是
netdev_max_backlog
,它表示 linux 核心從網卡驅動中讀取封包後可以緩存的封包數量,預設是 1000,可以調大這個值,比如設定成 2000: sudo sysctl -w net.core.netdev_max_backlog=2000
系統負載過高
系統 CPU、memory、IO 負載過高都有可能導緻網絡丢包,比如 CPU 如果負載過高,系統沒有時間進行封包的 checksum 計算、複制記憶體等操作,進而導緻網卡或者 socket buffer 出丢包;
memory 負載過高,會應用程式處理過慢,無法及時處理封包;
IO 負載過高,CPU 都用來響應 IO wait,沒有時間處理緩存中的 UDP 封包。
linux 系統本身就是互相關聯的系統,任何一個元件出現問題都有可能影響到其他元件的正常運作。
對于系統負載過高,要麼是應用程式有問題,要麼是系統不足。對于前者需要及時發現,debug 和修複;對于後者,也要及時發現并擴容。
應用丢包
上面提到系統的 UDP buffer size,調節的 sysctl 參數隻是系統允許的最大值,每個應用程式在建立 socket 時需要設定自己 socket buffer size 的值。
linux 系統會把接受到的封包放到 socket 的 buffer 中,應用程式從 buffer 中不斷地讀取封包。是以這裡有兩個和應用有關的因素會影響是否會丢包:socket buffer size 大小以及應用程式讀取封包的速度。
對于第一個問題,可以在應用程式初始化 socket 的時候設定 socket receive buffer 的大小,比如下面的代碼把 socket buffer 設定為 20MB:
uint64_t receive_buf_size = 20*1024*1024; //20 MB
setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &receive_buf_size, sizeof(receive_buf_size));
如果不是自己編寫和維護的程式,修改應用代碼是件不好甚至不太可能的事情。
很多應用程式會提供配置參數來調節這個值,請參考對應的官方文檔;如果沒有可用的配置參數,隻能給程式的開發者提 issue 了。
很明顯,增加應用的 receive buffer 會減少丢包的可能性,但同時會導緻應用使用更多的記憶體,是以需要謹慎使用。
另外一個因素是應用讀取 buffer 中封包的速度,對于應用程式來說,處理封包應該采取異步的方式
包丢在什麼地方
想要詳細了解 linux 系統在執行哪個函數時丢包的話,可以使用
dropwatch
工具,它監聽系統丢包資訊,并列印出丢包發生的函數位址:
通過這些資訊,找到對應的核心代碼處,就能知道核心在哪個步驟中把封包丢棄,以及大緻的丢包原因。
此外,還可以使用 linux perf 工具監聽
kfree_skb
(把網絡封包丢棄時會調用該函數) 事件的發生:
關于 perf 指令的使用和解讀,網上有很多文章可以參考。
總結
● UDP 本身就是無連接配接不可靠的協定,适用于封包偶爾丢失也不影響程式狀态的場景,比如視訊、音頻、遊戲、監控等。對封包可靠性要求比較高的應用不要使用 UDP,推薦直接使用 TCP。當然,也可以在應用層做重試、去重保證可靠性
● 如果發現伺服器丢包,首先通過監控檢視系統負載是否過高,先想辦法把負載降低再看丢包問題是否消失
● 如果系統負載過高,UDP 丢包是沒有有效解決方案的。如果是應用異常導緻 CPU、memory、IO 過高,請及時定位異常應用并修複;如果是資源不夠,監控應該能及時發現并快速擴容
● 對于系統大量接收或者發送 UDP 封包的,可以通過調節系統和程式的 socket buffer size 來降低丢包的機率
● 應用程式在處理 UDP 封包時,要采用異步方式,在兩次接收封包之間不要有太多的處理邏輯
原文釋出時間為:2018-10-15
本文作者:Cizixs
本文來自雲栖社群合作夥伴“
高效運維”,了解相關資訊可以關注“
”。