【乾貨】你真的需要了解一下 Linux 系統 UDP 丟包問題分析思路

2021-02-15 Linux架構師訓練營

在開始之前,我們先用一張圖解釋 linux 系統接收網絡報文的過程。

1、首先網絡報文通過物理網線發送到網卡

2、網絡驅動程序會把網絡中的報文讀出來放到 ring buffer 中,這個過程使用 DMA(Direct Memory Access),不需要 CPU 參與

3、內核從 ring buffer 中讀取報文進行處理,執行 IP 和 TCP/UDP 層的邏輯,最後把報文放到應用程式的 socket buffer 中

4、應用程式從 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 發送報文)的統計數據:

# ifconfig enp1
enp2s0f1: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether 04:b0:e7:fa:75:9d  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device memory 0x92200000-922fffff

此外,linux 系統也提供了各個網絡協議的丟包信息,可以使用 netstat -s 命令查看,加上 --udp 可以只看 UDP 相關的報文數據:

# netstat -s -u
IcmpMsg:
    InType0: 17
    InType3: 75
    InType8: 77
    OutType0: 77
    OutType3: 692
    OutType8: 249
Udp:
    5728807 packets received
    12 packets to unknown port received.
    0 packet receive errors
    982710 packets sent
    0 receive buffer errors
    0 send buffer errors
UdpLite:
IpExt:
    InNoRoutes: 3
    InBcastPkts: 497633
    InOctets: 1044710406807
    OutOctets: 17460621991142
    InBcastOctets: 114600482
    InNoECTPkts: 2886955071

對於上面的輸出,關注下面的信息來查看 UDP 丟包的情況:

packet receive errors 不為空,並且在一直增長說明系統有 UDP 丟包

packets to unknown port received 表示系統接收到的 UDP 報文所在的目標埠沒有應用在監聽,一般是服務沒有啟動導致的,並不會造成嚴重的問題

receive buffer errors 表示因為 UDP 的接收緩存太小導致丟包的數量

NOTE:並不是丟包數量不為零就有問題,對於 UDP 來說,如果有少量的丟包很可能是預期的行為,比如丟包率(丟包數量/接收報文數量)在萬分之一甚至更低。

網卡或者驅動丟包

之前講過,如果 ethtool -S eth0 中有 rx_***_errors 那麼很可能是網卡有問題,導致系統丟包,需要聯繫伺服器或者網卡供應商進行處理。

# ethtool -S enp1 | grep rx_ | grep errors
     rx_crc_errors: 0
     rx_missed_errors: 0
     rx_long_length_errors: 0
     rx_short_length_errors: 0
     rx_align_errors: 0
     rx_errors: 0
     rx_length_errors: 0
     rx_over_errors: 0
     rx_frame_errors: 0
     rx_fifo_errors: 0

netstat -i 也會提供每個網卡的接發報文以及丟包的情況,正常情況下輸出中 error 或者 drop 應該為 0。

如果硬體或者驅動沒有問題,一般網卡丟包是因為設置的緩存區(ring buffer)太小,可以使用 ethtool 命令查看和設置網卡的 ring buffer。

ethtool -g 可以查看某個網卡的 ring buffer,比如下面的例子

# ethtool -g enp1
Ring parameters for enp2s0f1:
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

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
net.core.rmem_max = 26214400

也可以修改 /etc/sysctl.conf 中對應的參數在下次啟動時讓參數保持生效。

如果報文報文過大,可以在發送方對數據進行分割,保證每個報文的大小在 MTU 內。

另外一個可以配置的參數是 netdev_max_backlog,它表示 linux 內核從網卡驅動中讀取報文後可以緩存的報文數量,默認是 1000,可以調大這個值,比如設置成 2000:

# sudo sysctl -w net.core.netdev_max_backlog=2000
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 工具,它監聽系統丟包信息,並列印出丟包發生的函數地址:

# dropwatch -l kas
Initalizing kallsyms db
dropwatch> start
Enabling monitoring...
Kernel monitoring activated.
Issue Ctrl-C to stop monitoring

1 drops at tcp_v4_do_rcv+cd (0xffffffff81799bad)
10 drops at tcp_v4_rcv+80 (0xffffffff8179a620)
1 drops at sk_stream_kill_queues+57 (0xffffffff81729ca7)
4 drops at unix_release_sock+20e (0xffffffff817dc94e)
1 drops at igmp_rcv+e1 (0xffffffff817b4c41)
1 drops at igmp_rcv+e1 (0xffffffff817b4c41)

通過這些信息,找到對應的內核代碼處,就能知道內核在哪個步驟中把報文丟棄,以及大致的丟包原因。本人在排查這個問題過程中更傾向於在各個機器抓包,這個方法更適合追蹤自身業務出現問題導致丟包,如下所示:

tcpdump -i 網絡接口名稱 udp port 2020 -s0 -XX -nn

此外,還可以使用 linux perf 工具監聽 kfree_skb(把網絡報文丟棄時會調用該函數) 事件的發生:

sudo perf record -g -a -e skb:kfree_skb
sudo perf script

關於 perf 命令的使用和解讀,網上有很多文章可以參考。

總結

UDP 本身就是無連接不可靠的協議,適用於報文偶爾丟失也不影響程序狀態的場景,比如視頻、音頻、遊戲、監控等。對報文可靠性要求比較高的應用不要使用 UDP,推薦直接使用 TCP。當然,也可以在應用層做重試、去重保證可靠性;

如果發現伺服器丟包,首先通過監控查看系統負載是否過高,先想辦法把負載降低再看丟包問題是否消失;

如果系統負載過高,UDP 丟包是沒有有效解決方案的。如果是應用異常導致 CPU、memory、IO 過高,請及時定位異常應用並修復;如果是資源不夠,監控應該能及時發現並快速擴容;

對於系統大量接收或者發送 UDP 報文的,可以通過調節系統和程序的 socket buffer size 來降低丟包的概率;

應用程式在處理 UDP 報文時,要採用異步方式,在兩次接收報文之間不要有太多的處理邏輯。

相關焦點

  • ethtool 原理介紹和解決網卡丟包排查思路
    這次想分享的話題是比較常見伺服器網卡丟包現象排查思路,如果你是想了解點對點的丟包解決思路涉及面可能就比較廣,不妨先參考之前的文章如何使用 MTR 診斷網絡問題[2],對於 Linux 常用的網卡丟包分析工具自然是 ethtool。 ethtool 用於查看和修改網絡設備(尤其是有線乙太網設備)的驅動參數和硬體設置。
  • TCP與UDP區別趣圖 小技巧:快速判斷udp,tcp埠是否同通暢
    2、發送的包巨大丟包:雖然send方法會幫你做大包切割成小包發送的事情,但包太大也不行。例如超過50K的一個udp包,不切割直接通過send方法發送也會導致這個包丟失。這種情況需要切割成小包再逐個send。 3、發送的包較大,超過接受者緩存導致丟包:包超過mtu size數倍,幾個大的udp包可能會超過接收者的緩衝,導致丟包。
  • 學習Linux系統需要明白的一些事情
    直到有一次找工作的時候,看見一個公司招運維人員,抱著試試的態度去跟面試官了解了解,比較幸運的是人家願意試用3個月,然後就正式邁出人生職業的第一步,在實習當中才接觸到linux,然後跟著師父一步一步的深入了解linux,我也以我6年的運維經歷及學習linux的經驗跟大夥分享下我的一些看法,當然,在IT行業,分享是一種精神,也是一種美德。
  • 當運行 Linux 內核的機器死機時……
    【CSDN 編者按】事件陷入死地無可挽救之際,可能會有人選擇不了了之,有人選擇就此放棄……但換個思路想一想,既然都無可挽回了,那幹嘛不試試弄點有價值的信息回來?Linux 內核在發生 soft lockup 的時候,是可以 ping 通的,只要沒有關中斷,ping 通一般沒有問題。既然可以 ping 通,何必不帶回一些真正重要的信息而不僅僅是 echo 的 reply?且慢,你可能會覺得這一切沒有意義,懂 kdump 的人都會這麼抬槓,畢竟如果這個時候讓內核 panic 掉,保留一個 vmcore,事後便可以隨便分析了。
  • linux - 你的伺服器丟包了?
    分析原因通過日誌可以查看到rsync日誌同步的速度比平時慢了9倍。通過zabbix監控看到網絡雖然比較繁忙,但是還沒有達到瓶頸。所以猜測:丟包了!!!RX dropped: 表示數據包已經進入了 Ring Buffer,但是由於內存不夠等系統原因,導致在拷貝到內存的過程中被丟棄。
  • Linux系統安全設置 全面堅固你的系統
    這些服務經常會被遊手好閒的網民按其需要用來作為系統突破的切入點,這不僅是Linux 的局限——久經風霜的商業UNIX也提供此類服務,而且也會被突破。不用抱怨和指責,新系統的鎖定(堅固系統的專業說法)是非常重要的。信不信由你,一個Linux 系統的堅固過程是不需要過多的系統安全專門知識。實際上,你可以在5 分鐘之內就可以將百分之九十的不可靠因素屏蔽掉。
  • Linux內核分析 | CVE-2017-1000112(UDP Fragment Offload)
    如果追加數據失敗,則調用 udp_flush_pending_frame 丟棄數據。當我們建立好socket並初始化之後,第一次send,帶上標記為MSG_MORE告訴系統我們接下來還有數據要發送。此時走UFO路徑如果要發送的是UDP數據包,且系統支持UFO,並且需要分片(length > mtu),那麼 send() 最終會進入:ip_ufo_append_data
  • 深入作業系統,從內核理解網絡包的接收過程(Linux篇)
    4.1 創建ksoftirqd內核線程Linux的軟中斷都是在專門的內核線程(ksoftirqd)中進行的,因此我們非常有必要看一下這些進程是怎麼初始化的,這樣我們才能在後面更準確地了解收包過程。該進程數量不是1個,而是N個,其中N等於你的機器的核數。
  • 為什麼 TCP 會被 UDP 取代
    轉自:真沒什麼邏輯為什麼這麼設計(Why's THE Design)是一系列關於計算機領域中程序設計決策的文章,我們在這個系列的每一篇文章中都會提出一個具體的問題並從不同的角度討論這種設計的優缺點、對具體實現造成的影響。如果你有想要了解的問題,可以在文章下面留言。
  • 為什麼 TCP 會被 UDP 取代?
    為什麼這麼設計(Why's THE Design)是一系列關於計算機領域中程序設計決策的文章,我們在這個系列的每一篇文章中都會提出一個具體的問題並從不同的角度討論這種設計的優缺點、對具體實現造成的影響。如果你有想要了解的問題,可以在文章下面留言。
  • Linux系統從入門到放棄?
    Linux是一個命令行組成的作業系統,精髓在命令行,學習如何在Linux環境中執行linux命令,包括有關文件、目錄、文件系統、進程等概念,如何使用相應的命令對文件、目錄、進程等進行管理,了解遇到問題時,如何找到幫助信息等等。都將是我們學習入門Linux的第二大步。
  • Windows主機日誌分析辦法與思路
    在做日誌分析前,首先我們針對此項工作需要有一個清晰的思路:查看哪些主機的日誌(篩選對象)——>在哪裡查看(取樣)——>怎麼查看(研判分析)——>做好記錄、保留關鍵截圖——>上報並處置事件閉環。
  • Linux系統入門命令學習經驗
    此時此刻我想和大家分享一下我在學習linux過程中的一些經驗和教訓,如果有人能夠正好看到我的這篇文章,希望能夠讓想學習linux的同學多少獲得一點經驗,少走一些彎路。能夠比較簡單、快捷的迅速掌握知識是我們學習的目的,但是我們平常的學習中大部分都有一些人在指導。
  • 了解linux系統目錄,home,lib,lost+found,media,mnt,opt!
    linux小白到大神的成長之路:了解linux系統目錄,home,lib,lost+found,media,mnt,opt!本經驗由宗龍龍原創,全文共600多字,閱讀需要14分鐘,如果文中存在錯誤,還請大家多多指點,我會積極改進的!
  • 面試Python工程師會問哪些問題?需要準備什麼?
    我從來不問google可獲得的答案的問題, 只是問問面試者「在過去的工作中,遇到的Ta認為較有成就感的一件事情是什麼」和「如果出現了一個google不到的問題,你會怎麼解決」這樣的問題。2.我從來不問Python語法這種看書和google就能學會的問題,我只想了解Ta是否願意去學。舉個例子,我會讓面試者「講講日常開發中都用到了那些Python內置的模塊」,基本上說完我就能評估出他的能力和風格,繼而就是再問一些問題去驗證我的評估是不是準確。6.
  • Linux入侵排查思路
    一般該帳號是具有登錄權限的,正常用戶的宿主目錄一般都在/home目錄下為了能夠更快速的查看可登錄的帳號一般會採用cat /etc/passwd |grep "/bin/sh"cat /etc/passwd"/bin/bash"宿主目錄為 /var/www 的用戶命令解釋器一般不會是/bin/bash或者/bin/sh的,這種用戶需要我們特別注意一下
  • 淺談分析Arm linux 內核移植及系統初始化的過程二
    );//setup processor and machine and destinate some pointers for do_initcalls() s5、淺談分析Arm linux 內核移植及系統初始化的過程 諮詢QQ:313807838// for example init_machine
  • Proxy-Go v3.3 發布,socks 新增 UDP 支持並增強代理穩定性
    Proxy是golang實現的高性能http,https,websocket,tcp,udp,socks5代理伺服器,支持正向代理、內網穿透、SSH中轉。更新內容:修復了socks代理模式對證書文件的判斷邏輯.增強了http代理,socks代理的ssh中轉模式的穩定性.
  • Linux input子系統編程、分析與模板
    內核中的輸入子系統自底向上分為設備驅動層,輸入核心層,事件處理層。由於每種輸入的設備上報的事件都各有不同,所以為了應用層能夠很好識別上報的事件,內核中也為應用層封裝了標準的接口來描述一個事件,這些接口在"/include/upai/linux/input"中。