Linux性能優化實戰,網絡丟包問題分析
在當今數字化時代,無論是搭建服務器、開發網絡應用,還是進行云計算部署,Linux 系統都扮演著舉足輕重的角色。作為一名運維人員或開發者,你肯定希望自己的 Linux 系統能夠高效穩定地運行。但當網絡丟包問題出現時,一切都變得糟糕起來,服務器響應遲緩,應用程序頻繁報錯,用戶體驗直線下降。
今天,我就帶大家深入 Linux 性能優化的實戰領域,一起揪出網絡丟包這個 “罪魁禍首”,從原理分析到排查方法,再到解決方案,全方位為你答疑解惑 ,讓你的 Linux 系統重回高性能狀態。
一、網絡丟包概述
對于 Linux 系統的使用者來說,網絡性能的優劣直接關系到系統的整體表現。而在網絡性能問題中,網絡丟包堪稱最為棘手的難題之一,它就像隱藏在暗處的殺手,悄無聲息地侵蝕著系統的性能。想象一下,當你在服務器上部署了一個關鍵的應用服務,滿懷期待地等待用戶的訪問和使用。然而,用戶卻頻繁反饋訪問速度極慢,甚至出現連接中斷的情況。經過一番排查,你發現罪魁禍首竟然是網絡丟包。這時候,你就會深刻地意識到,網絡丟包問題絕不是一個可以忽視的小麻煩。
從專業角度來看,網絡丟包會帶來一系列嚴重的后果。最直觀的就是網絡延遲的顯著增加。當數據包在傳輸過程中被丟棄,接收方就無法及時收到完整的數據,這就需要發送方重新發送這些丟失的數據包。重傳的過程無疑會消耗額外的時間,導致數據傳輸的延遲大幅上升。在一些對實時性要求極高的應用場景中,如在線游戲、視頻會議等,哪怕是幾毫秒的延遲增加都可能帶來極差的用戶體驗。在在線游戲中,延遲的增加可能導致玩家的操作出現卡頓,無法及時響應游戲中的各種事件,嚴重影響游戲的流暢性和競技性;在視頻會議中,延遲則可能使畫面出現卡頓、聲音不同步等問題,讓溝通變得異常困難。
網絡丟包還會導致吞吐量的降低。吞吐量是指單位時間內成功傳輸的數據量,它是衡量網絡性能的重要指標之一。當丟包發生時,一部分數據無法正常傳輸,這就必然會導致實際的吞吐量下降。對于一些大數據傳輸的場景,如文件下載、數據備份等,吞吐量的降低會大大延長傳輸時間,降低工作效率。如果你需要從遠程服務器下載一個大型文件,原本預計幾個小時就能完成的下載任務,可能因為網絡丟包導致下載時間延長數倍,甚至可能因為丟包過于嚴重而導致下載失敗,需要重新開始。
對于基于 TCP 協議的應用來說,丟包更是意味著網絡擁塞和重傳。TCP 協議具有可靠性機制,當它檢測到數據包丟失時,會自動觸發重傳機制,以確保數據的完整性。然而,頻繁的重傳不僅會增加網絡流量,還會進一步加劇網絡擁塞,形成一種惡性循環。在高并發的網絡環境中,這種惡性循環可能會導致整個網絡的癱瘓,使所有依賴網絡的應用都無法正常運行。
網絡丟包對 Linux 系統性能的影響是多方面的,它不僅會降低用戶體驗,還會影響業務的正常運行,給企業帶來巨大的損失。因此,解決網絡丟包問題刻不容緩,這也是我們今天深入探討 Linux 性能優化實戰 —— 網絡丟包問題分析的重要原因。
在開始之前,我們先用一張圖解釋 linux 系統接收網絡報文的過程:
- 首先網絡報文通過物理網線發送到網卡
- 網絡驅動程序會把網絡中的報文讀出來放到 ring buffer 中,這個過程使用 DMA(Direct Memory Access),不需要 CPU 參與
- 內核從 ring buffer 中讀取報文進行處理,執行 IP 和 TCP/UDP 層的邏輯,最后把報文放到應用程序的 socket buffer 中
- 應用程序從 socket buffer 中讀取報文進行處理
圖片
二、探尋“兇手”:丟包可能發生在哪
當網絡丟包問題出現時,就如同一場懸疑案件,我們需要抽絲剝繭,從各個層面去探尋 “兇手”,也就是丟包發生的原因。在 Linux 系統中,丟包可能發生在網絡協議棧的各個層次,每個層次都有其獨特的丟包原因和排查方法。
2.1收包流程:數據包的 “入境之路”
圖片
當網卡接收到報文時,這場 “入境之旅” 就開啟了。首先,網卡通過 DMA(直接內存訪問)技術,以極高的效率將數據包拷貝到 RingBuf(環形緩沖區)中,就好比貨物被快速卸到了一個臨時倉庫。緊接著,網卡向 CPU 發起一個硬中斷,就像吹響了緊急集合哨,通知 CPU 有數據抵達 “國門”。
CPU 迅速響應,開始執行對應的硬中斷處理例程,在這個例程里,它會將數據包的相關信息放入每 CPU 變量 poll_list 中,隨后觸發一個收包軟中斷,把后續的精細活兒交給軟中斷去處理。對應 CPU 的軟中斷線程 ksoftirqd 就登場了,它負責處理網絡包接收軟中斷,具體來說,就是執行 net_rx_action () 函數。
在這個函數的 “指揮” 下,數據包從 RingBuf 中被小心翼翼地取出,然后進入協議棧,開啟層層闖關。從鏈路層開始,檢查報文合法性,剝去幀頭、幀尾,接著進入網絡層,判斷包的走向,若是發往本機,再傳遞到傳輸層。最終,數據包被妥妥地放到 socket 的接收隊列中,等待應用層隨時來收取,至此,數據包算是順利 “入境”,完成了它的收包流程。
2.2發包流程:數據包的 “出境之旅”
圖片
應用程序要發送數據時,數據包的 “出境之旅” 便啟程了。首先,應用程序調用 Socket API(比如 sendmsg)發送網絡包,這一操作觸發系統調用,使得數據從用戶空間拷貝到內核空間,同時,內核會為其分配一個 skb(sk_buff 結構體,它可是數據包在內核中的 “代言人”,承載著各種關鍵信息),并將數據封裝其中。接著,skb 進入協議棧,開始自上而下的 “闖關升級”。
在傳輸層,會為數據添加 TCP 頭或 UDP 頭,進行擁塞控制、滑動窗口等一系列精細操作;到了網絡層,依據目標 IP 地址查找路由表,確定下一跳,填充 IP 頭中的源和目標 IP 地址、TTL 等關鍵信息,還可能進行 skb 切分,同時要經過 netfilter 框架的 “安檢”,判斷是否符合過濾規則。
之后,在鄰居子系統填充目的 MAC 地址,再進入網絡設備子系統,skb 被放入發送隊列 RingBuf 中,等待網卡發送。網卡發送完成后,會向 CPU 發出一個硬中斷,告知 “任務完成”,這個硬中斷又會觸發軟中斷,在軟中斷處理函數中,對 RingBuf 進行清理,把已經發送成功的數據包殘留信息清除掉,就像清理運輸后的車廂,為下一次運輸做好準備,至此,數據包順利 “出境”,完成了它的發包流程。
三、鏈路層詳解
當鏈路層由于緩沖區溢出等原因導致網卡丟包時,Linux 會在網卡收發數據的統計信息中記錄下收發錯誤的次數。鏈路層是網絡通信的基礎,它負責將網絡層傳來的數據封裝成幀,并通過物理介質進行傳輸。鏈路層丟包通常是由于硬件故障、網絡擁塞或者配置錯誤等原因導致的。當鏈路層由于緩沖區溢出等原因導致網卡丟包時,Linux 會在網卡收發數據的統計信息中記錄下收發錯誤的次數。
我們可以通過 ethtool 或者 netstat 命令,來查看網卡的丟包記錄:
netstat -i
Kernel Interface table
Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 100 31 0 0 0 8 0 0 0 BMRU
lo 65536 0 0 0 0 0 0 0 0 LRU
RX-OK、RX-ERR、RX-DRP、RX-OVR ,分別表示接收時的總包數、總錯誤數、進入 Ring Buffer 后因其他原因(如內存不足)導致的丟包數以及 Ring Buffer 溢出導致的丟包數。
TX-OK、TX-ERR、TX-DRP、TX-OVR 也代表類似的含義,只不過是指發送時對應的各個指標。
這里我們沒有發現任何錯誤,說明虛擬網卡沒有丟包。不過要注意,如果用 tc 等工具配置了 QoS,那么 tc 規則導致的丟包,就不會包含在網卡的統計信息中。所以接下來,我們還要檢查一下 eth0 上是否配置了 tc 規則,并查看有沒有丟包。添加 -s 選項,以輸出統計信息:
tc -s qdisc show dev eth0
qdisc netem 800d: root refcnt 2 limit 1000 loss 30%
Sent 432 bytes 8 pkt (dropped 4, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
可以看到, eth0 上配置了一個網絡模擬排隊規則(qdisc netem),并且配置了丟包率為 30%(loss 30%)。再看后面的統計信息,發送了 8 個包,但是丟了 4個。看來應該就是這里導致 Nginx 回復的響應包被 netem 模塊給丟了。
既然發現了問題,解決方法也很簡單,直接刪掉 netem 模塊就可以了。執行下面的命令,刪除 tc 中的 netem 模塊:
tc qdisc del dev eth0 root netem loss 30%
刪除后,重新執行之前的 hping3 命令,看看現在還有沒有問題:
hping3 -c 10 -S -p 80 192.168.0.30
HPING 192.168.0.30 (eth0 192.168.0.30): S set, 40 headers + 0 data bytes
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=5120 rtt=7.9 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=2 win=5120 rtt=1003.8 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=5120 rtt=7.6 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=6 win=5120 rtt=7.4 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=9 win=5120 rtt=3.0 ms
--- 192.168.0.30 hping statistic ---
10 packets transmitted, 5 packets received, 50% packet loss
round-trip min/avg/max = 3.0/205.9/1003.8 ms
不幸的是,從 hping3 的輸出中可以看到還是 50% 的丟包,RTT 的波動也仍舊很大,從 3ms 到 1s。顯然,問題還是沒解決,丟包還在繼續發生。不過,既然鏈路層已經排查完了,我們就繼續向上層分析,看看網絡層和傳輸層有沒有問題。
四、網絡層和傳輸層
在網絡層和傳輸層中,引發丟包的因素非常多。不過,其實想確認是否丟包,是非常簡單的事,因為 Linux 已經為我們提供了各個協議的收發匯總情況。
⑴網絡層
網絡層負責將數據包從源地址傳輸到目的地址,它主要處理路由選擇、IP 地址解析等功能。網絡層丟包可能是由于路由失敗、組包大小超過 MTU(最大傳輸單元)等原因引起的。當數據包的大小超過了網絡中某條鏈路的 MTU 時,數據包就需要被分片傳輸,如果分片過程出現問題,或者在重組過程中丟失了部分分片,就會導致丟包。我們可以通過netstat -s命令查看 IP 層的統計信息,其中IpInReceives表示接收到的 IP 數據包總數,IpInDelivers表示成功交付給上層協議的 IP 數據包數,如果兩者之間的差值較大,就可能存在網絡層丟包的情況。還可以查看IpOutNoRoutes指標,它表示因為找不到路由而丟棄的數據包數,如果這個值不斷增加,說明可能存在路由問題導致丟包。
⑵傳輸層
傳輸層負責為應用層提供端到端的通信服務,常見的傳輸層協議有 TCP 和 UDP。傳輸層丟包可能是由于端口未監聽、資源占用超過內核限制等原因造成的。在高并發的網絡環境中,如果應用程序創建了大量的 TCP 連接,而系統資源(如文件描述符、內存等)有限,就可能導致部分連接無法正常建立或維持,從而出現丟包現象。我們可以通過netstat -s命令查看 TCP 和 UDP 協議的統計信息,比如TcpRetransSegs表示 TCP 重傳的數據包數,如果這個值較大,說明可能存在傳輸層丟包導致的重傳。UdpInErrors表示接收到的 UDP 錯誤數據包數,如果該值不為零,也提示可能存在 UDP 丟包問題。
執行 netstat -s 命令,可以看到協議的收發匯總,以及錯誤信息:
netstat -s
#輸出
Ip:
Forwarding: 1 //開啟轉發
31 total packets received //總收包數
0 forwarded //轉發包數
0 incoming packets discarded //接收丟包數
25 incoming packets delivered //接收的數據包數
15 requests sent out //發出的數據包數
Icmp:
0 ICMP messages received //收到的ICMP包數
0 input ICMP message failed //收到ICMP失敗數
ICMP input histogram:
0 ICMP messages sent //ICMP發送數
0 ICMP messages failed //ICMP失敗數
ICMP output histogram:
Tcp:
0 active connection openings //主動連接數
0 passive connection openings //被動連接數
11 failed connection attempts //失敗連接嘗試數
0 connection resets received //接收的連接重置數
0 connections established //建立連接數
25 segments received //已接收報文數
21 segments sent out //已發送報文數
4 segments retransmitted //重傳報文數
0 bad segments received //錯誤報文數
0 resets sent //發出的連接重置數
Udp:
0 packets received
...
TcpExt:
11 resets received for embryonic SYN_RECV sockets //半連接重置數
0 packet headers predicted
TCPTimeouts: 7 //超時數
TCPSynRetrans: 4 //SYN重傳數
...
etstat 匯總了 IP、ICMP、TCP、UDP 等各種協議的收發統計信息。不過,我們的目的是排查丟包問題,所以這里主要觀察的是錯誤數、丟包數以及重傳數。可以看到,只有 TCP 協議發生了丟包和重傳,分別是:
- 11 次連接失敗重試(11 failed connection attempts)
- 4 次重傳(4 segments retransmitted)
- 11 次半連接重置(11 resets received for embryonic SYN_RECV sockets)
- 4 次 SYN 重傳(TCPSynRetrans)
- 7 次超時(TCPTimeouts)
這個結果告訴我們,TCP 協議有多次超時和失敗重試,并且主要錯誤是半連接重置。換句話說,主要的失敗,都是三次握手失敗。不過,雖然在這兒看到了這么多失敗,但具體失敗的根源還是無法確定。
五、iptables規則
iptables 是 Linux 系統中常用的防火墻工具,它可以根據用戶定義的規則對網絡數據包進行過濾和處理。如果 iptables 的規則配置不當,就可能導致數據包被錯誤地丟棄。我們可以通過iptables -L -n命令查看當前的 iptables 規則,檢查是否存在不合理的規則,如錯誤的端口過濾、源地址或目的地址限制等。也可以使用iptables -v -L命令查看規則的統計信息,了解哪些規則被頻繁匹配,從而判斷是否是 iptables 規則導致了丟包。
首先,除了網絡層和傳輸層的各種協議,iptables 和內核的連接跟蹤機制也可能會導致丟包。所以,這也是發生丟包問題時我們必須要排查的一個因素。
先來看看連接跟蹤,要確認是不是連接跟蹤導致的問題,只需要對比當前的連接跟蹤數和最大連接跟蹤數即可。
# 主機終端中查詢內核配置
$ sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 262144
$ sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 182
可以看到,連接跟蹤數只有 182,而最大連接跟蹤數則是 262144。顯然,這里的丟包,不可能是連接跟蹤導致的。
接著,再來看 iptables。回顧一下 iptables 的原理,它基于 Netfilter 框架,通過一系列的規則,對網絡數據包進行過濾(如防火墻)和修改(如 NAT)。這些 iptables 規則,統一管理在一系列的表中,包括 filter、nat、mangle(用于修改分組數據) 和 raw(用于原始數據包)等。而每張表又可以包括一系列的鏈,用于對 iptables 規則進行分組管理。
對于丟包問題來說,最大的可能就是被 filter 表中的規則給丟棄了。要弄清楚這一點,就需要我們確認,那些目標為 DROP 和 REJECT 等會棄包的規則,有沒有被執行到。可以直接查詢 DROP 和 REJECT 等規則的統計信息,看看是否為0。如果不是 0 ,再把相關的規則拎出來進行分析。
iptables -t filter -nvL
#輸出
Chain INPUT (policy ACCEPT 25 packets, 1000 bytes)
pkts bytes target prot opt in out source destination
6 240 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.29999999981
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 15 packets, 660 bytes)
pkts bytes target prot opt in out source destination
6 264 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.29999999981
從 iptables 的輸出中,你可以看到,兩條 DROP 規則的統計數值不是 0,它們分別在INPUT 和 OUTPUT 鏈中。這兩條規則實際上是一樣的,指的是使用 statistic 模塊,進行隨機 30% 的丟包。0.0.0.0/0 表示匹配所有的源 IP 和目的 IP,也就是會對所有包都進行隨機 30% 的丟包。看起來,這應該就是導致部分丟包的“罪魁禍首”了。
執行下面的兩條 iptables 命令,刪除這兩條 DROP 規則。
root@nginx:/# iptables -t filter -D INPUT -m statistic --mode random --probability 0.30 -j DROP
root@nginx:/# iptables -t filter -D OUTPUT -m statistic --mode random --probability 0.30 -j DROP
再次執行剛才的 hping3 命令,看看現在是否正常
hping3 -c 10 -S -p 80 192.168.0.30
#輸出
HPING 192.168.0.30 (eth0 192.168.0.30): S set, 40 headers + 0 data bytes
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=5120 rtt=11.9 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=1 win=5120 rtt=7.8 ms
...
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=9 win=5120 rtt=15.0 ms
--- 192.168.0.30 hping statistic ---
10 packets transmitted, 10 packets received, 0% packet loss
round-trip min/avg/max = 3.3/7.9/15.0 ms
這次輸出你可以看到,現在已經沒有丟包了,并且延遲的波動變化也很小。看來,丟包問題應該已經解決了。
不過,到目前為止,我們一直使用的 hping3 工具,只能驗證案例 Nginx 的 80 端口處于正常監聽狀態,卻還沒有訪問 Nginx 的 HTTP 服務。所以,不要匆忙下結論結束這次優化,我們還需要進一步確認,Nginx 能不能正常響應 HTTP 請求。我們繼續在終端二中,執行如下的 curl 命令,檢查 Nginx 對 HTTP 請求的響應:
$ curl --max-time 3 http://192.168.0.30
curl: (28) Operation timed out after 3000 milliseconds with 0 bytes received
奇怪,hping3 的結果顯示Nginx 的 80 端口是正常狀態,為什么還是不能正常響應 HTTP 請求呢?別忘了,我們還有個大殺器——抓包操作。看來有必要抓包看看了。
六、tcpdump命令工具使用
執行下面的 tcpdump 命令,抓取 80 端口的包如下:
tcpdump -i eth0 -nn port 80
#輸出
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
然后,切換到終端二中,再次執行前面的 curl 命令:
curl --max-time 3 http://192.168.0.30
curl: (28) Operation timed out after 3000 milliseconds with 0 bytes received
等到 curl 命令結束后,再次切換回終端一,查看 tcpdump 的輸出:
14:40:00.589235 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [S], seq 332257715, win 29200, options [mss 1418,sackOK,TS val 486800541 ecr 0,nop,wscale 7], length 0
14:40:00.589277 IP 172.17.0.2.80 > 10.255.255.5.39058: Flags [S.], seq 1630206251, ack 332257716, win 4880, options [mss 256,sackOK,TS val 2509376001 ecr 486800541,nop,wscale 7], length 0
14:40:00.589894 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 486800541 ecr 2509376001], length 0
14:40:03.589352 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [F.], seq 76, ack 1, win 229, options [nop,nop,TS val 486803541 ecr 2509376001], length 0
14:40:03.589417 IP 172.17.0.2.80 > 10.255.255.5.39058: Flags [.], ack 1, win 40, options [nop,nop,TS val 2509379001 ecr 486800541,nop,nop,sack 1 {76:77}], length 0
從 tcpdump 的輸出中,我們就可以看到:
- 前三個包是正常的 TCP 三次握手,這沒問題;
- 但第四個包卻是在 3 秒以后了,并且還是客戶端(VM2)發送過來的 FIN 包,說明客戶端的連接關閉了
根據 curl 設置的 3 秒超時選項,你應該能猜到,這是因為 curl 命令超時后退出了。用 Wireshark 的 Flow Graph 來表示,你可以更清楚地看到上面這個問題:
圖片
這里比較奇怪的是,我們并沒有抓取到 curl 發來的 HTTP GET 請求。那究竟是網卡丟包了,還是客戶端就沒發過來呢?
可以重新執行 netstat -i 命令,確認一下網卡有沒有丟包問題:
netstat -i
Kernel Interface table
Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 100 157 0 344 0 94 0 0 0 BMRU
lo 65536 0 0 0 0 0 0 0 0 LRU
從 netstat 的輸出中,你可以看到,接收丟包數(RX-DRP)是 344,果然是在網卡接收時丟包了。不過問題也來了,為什么剛才用 hping3 時不丟包,現在換成 GET 就收不到了呢?還是那句話,遇到搞不懂的現象,不妨先去查查工具和方法的原理。我們可以對比一下這兩個工具:
- hping3 實際上只發送了 SYN 包;
- curl 在發送 SYN 包后,還會發送 HTTP GET 請求。HTTP GET本質上也是一個 TCP 包,但跟 SYN 包相比,它還攜帶了 HTTP GET 的數據。
通過這個對比,你應該想到了,這可能是 MTU 配置錯誤導致的。為什么呢?
其實,仔細觀察上面 netstat 的輸出界面,第二列正是每個網卡的 MTU 值。eth0 的 MTU只有 100,而以太網的 MTU 默認值是 1500,這個 100 就顯得太小了。當然,MTU 問題是很好解決的,把它改成 1500 就可以了。
ifconfig eth0 mtu 1500
修改完成后,再切換到終端二中,再次執行 curl 命令,確認問題是否真的解決了:
curl --max-time 3 http://192.168.0.30/
#輸出
<!DOCTYPE html>
<html>
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
非常不容易呀,這次終于看到了熟悉的 Nginx 響應,說明丟包的問題終于徹底解決了。
七、實戰演練:排查與解決 Nginx 丟包問題
理論上的分析固然重要,但實際操作才是檢驗真理的關鍵。下面,我們將通過一個具體的案例,以 Nginx 應用為例,深入探討如何在實際場景中排查和解決網絡丟包問題。
7.1模擬訪問與初步判斷
假設我們在一臺 Linux 服務器上部署了 Nginx 應用,現在懷疑它存在網絡丟包問題。我們首先使用 hping3 命令來模擬訪問 Nginx 服務。hping3 是一個功能強大的網絡工具,它可以發送各種類型的網絡數據包,幫助我們測試網絡的連通性和性能。執行以下命令:
hping3 -c 10 -S -p 80 192.168.0.30
在這個命令中,-c 10表示發送 10 個請求包,-S表示使用 TCP SYN 標志位,-p 80指定目標端口為 80,即 Nginx 服務默認的端口,192.168.0.30是 Nginx 服務器的 IP 地址。執行命令后,我們得到如下輸出:
HPING 192.168.0.30 (eth0 192.168.0.30): S set, 40 headers + 0 data bytes
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=3 win=5120 rtt=7.5 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=4 win=5120 rtt=7.4 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=5120 rtt=3.3 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=7 win=5120 rtt=3.0 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=6 win=5120 rtt=3027.2 ms
--- 192.168.0.30 hping statistic ---
10 packets transmitted, 5 packets received, 50% packet loss
round-trip min/avg/max = 3.0/609.7/3027.2 ms
從輸出結果中,我們可以清晰地看到,總共發送了 10 個請求包,但只收到了 5 個回復包,丟包率高達 50%。而且,每個請求的 RTT(往返時間)波動非常大,最小值只有 3.0ms,而最大值卻達到了 3027.2ms,這表明網絡中很可能存在丟包現象。
7.2鏈路層排查
初步判斷存在丟包問題后,我們首先從鏈路層開始排查。使用netstat -i命令查看虛擬網卡的丟包記錄:
netstat -i
得到如下輸出:
Kernel Interface table
Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 100 31 0 0 0 8 0 0 0 BMRU
lo 65536 0 0 0 0 0 0 0 0 LRUR
在這個輸出中,RX-OK表示接收時的總包數,RX-ERR表示總錯誤數,RX-DRP表示進入 Ring Buffer 后因其他原因(如內存不足)導致的丟包數,RX-OVR表示 Ring Buffer 溢出導致的丟包數,TX-OK至TX-OVR則表示發送時的相應指標。從這里可以看出,虛擬網卡的各項錯誤指標均為 0,說明虛擬網卡本身沒有丟包。
不過,如果使用tc等工具配置了 QoS(Quality of Service,服務質量),tc規則導致的丟包不會包含在網卡的統計信息中。因此,我們還需要檢查eth0上是否配置了tc規則,并查看是否有丟包。添加-s選項以輸出統計信息:
tc -s qdisc show dev eth0
輸出結果如下:
qdisc netem 800d: root refcnt 2 limit 1000 loss 30%
Sent 432 bytes 8 pkt (dropped 4, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
可以看到,eth0上配置了一個網絡模擬排隊規則qdisc netem,并且設置了丟包率為 30%(loss 30%)。從后面的統計信息可知,發送了 8 個包,但丟了 4 個。這很可能就是導致 Nginx 回復的響應包被netem模塊丟棄的原因。既然找到了問題,解決方法就很簡單,直接刪除netem模塊:
tc qdisc del dev eth0 root netem loss 30%
刪除后,重新執行hping3命令,看看問題是否解決。然而,從hping3的輸出中發現,仍然有 50% 的丟包,RTT 波動依舊很大,說明問題還未得到解決,需要繼續向上層排查。
網絡層和傳輸層排查
接下來,我們排查網絡層和傳輸層。在這兩層中,引發丟包的因素眾多,但確認是否丟包卻相對簡單,因為 Linux 已經為我們提供了各個協議的收發匯總情況。執行netstat -s命令,查看 IP、ICMP、TCP 和 UDP 等協議的收發統計信息:
netstat -s
輸出結果非常豐富,這里我們重點關注與丟包相關的信息。例如,從 TCP 協議的統計信息中,我們看到有多次超時和失敗重試,并且主要錯誤是半連接重置,這表明可能存在三次握手失敗的問題。這可能是由于網絡擁塞、端口被占用、防火墻限制等原因導致的。此時,我們需要進一步分析具體的錯誤原因,可以結合其他工具和命令,如lsof查看端口占用情況,檢查防火墻規則等。
7.3iptables 排查
iptables 是 Linux 系統中常用的防火墻工具,其規則配置不當可能導致數據包被丟棄。首先,我們檢查內核的連接跟蹤機制,查看當前的連接跟蹤數和最大連接跟蹤數:
cat /proc/sys/net/nf_conntrack_count
cat /proc/sys/net/nf_conntrack_max
假設連接跟蹤數只有 182,而最大連接跟蹤數是 262144,說明連接跟蹤數沒有達到上限,不存在因連接跟蹤數滿而導致丟包的問題。
接著,查看 iptables 規則,使用iptables -L -n命令:
iptables -L -n
在輸出的規則列表中,我們發現有兩條DROP規則,使用了statistic模塊進行隨機 30% 的丟包。這顯然是導致丟包的一個重要原因。我們將這兩條規則直接刪除,然后重新執行hping3命令。此時,hping3的輸出顯示已經沒有丟包,這說明 iptables 的錯誤規則是導致之前丟包的原因之一。
端口狀態檢查與進一步排查
雖然hping3驗證了 Nginx 的 80 端口處于正常監聽狀態,但還需要檢查 Nginx 對 HTTP 請求的響應。使用curl命令:
curl -w 'Http code: %{http_code}\\nTotal time:%{time_total}s\\n' -o /dev/null --connect-timeout 10 http://192.168.0.30/
結果發現連接超時,這表明雖然端口監聽正常,但 Nginx 在處理 HTTP 請求時可能存在問題。為了進一步分析,我們使用tcpdump命令抓包:
tcpdump -i eth0 -n tcp port 80
在另一個終端執行curl命令,然后查看tcpdump的輸出。發現前三個包是正常的 TCP 三次握手,但第四個包卻是在 3 秒后才收到,并且是客戶端發送過來的 FIN 包,這說明客戶端的連接已經關閉。
重新執行netstat -i命令,檢查網卡是否有丟包,發現果然是在網卡接收時丟包了。進一步檢查最大傳輸單元 MTU(Maximum Transmission Unit):
ifconfig eth0 | grep MTU
發現eth0的 MTU 只有 100,而以太網的 MTU 默認值是 1500。MTU 過小可能導致數據包在傳輸過程中需要分片,從而增加丟包的風險。我們將 MTU 修改為 1500:
ifconfig eth0 mtu 1500
再次執行curl命令,問題得到解決,Nginx 能夠正常響應 HTTP 請求。