Linux性能優(yōu)化實戰(zhàn),網(wǎng)絡丟包問題分析
在當今數(shù)字化時代,無論是搭建服務器、開發(fā)網(wǎng)絡應用,還是進行云計算部署,Linux 系統(tǒng)都扮演著舉足輕重的角色。作為一名運維人員或開發(fā)者,你肯定希望自己的 Linux 系統(tǒng)能夠高效穩(wěn)定地運行。但當網(wǎng)絡丟包問題出現(xiàn)時,一切都變得糟糕起來,服務器響應遲緩,應用程序頻繁報錯,用戶體驗直線下降。
今天,我就帶大家深入 Linux 性能優(yōu)化的實戰(zhàn)領域,一起揪出網(wǎng)絡丟包這個 “罪魁禍首”,從原理分析到排查方法,再到解決方案,全方位為你答疑解惑 ,讓你的 Linux 系統(tǒng)重回高性能狀態(tài)。
一、網(wǎng)絡丟包概述
對于 Linux 系統(tǒng)的使用者來說,網(wǎng)絡性能的優(yōu)劣直接關系到系統(tǒng)的整體表現(xiàn)。而在網(wǎng)絡性能問題中,網(wǎng)絡丟包堪稱最為棘手的難題之一,它就像隱藏在暗處的殺手,悄無聲息地侵蝕著系統(tǒng)的性能。想象一下,當你在服務器上部署了一個關鍵的應用服務,滿懷期待地等待用戶的訪問和使用。然而,用戶卻頻繁反饋訪問速度極慢,甚至出現(xiàn)連接中斷的情況。經(jīng)過一番排查,你發(fā)現(xiàn)罪魁禍首竟然是網(wǎng)絡丟包。這時候,你就會深刻地意識到,網(wǎng)絡丟包問題絕不是一個可以忽視的小麻煩。
從專業(yè)角度來看,網(wǎng)絡丟包會帶來一系列嚴重的后果。最直觀的就是網(wǎng)絡延遲的顯著增加。當數(shù)據(jù)包在傳輸過程中被丟棄,接收方就無法及時收到完整的數(shù)據(jù),這就需要發(fā)送方重新發(fā)送這些丟失的數(shù)據(jù)包。重傳的過程無疑會消耗額外的時間,導致數(shù)據(jù)傳輸?shù)难舆t大幅上升。在一些對實時性要求極高的應用場景中,如在線游戲、視頻會議等,哪怕是幾毫秒的延遲增加都可能帶來極差的用戶體驗。在在線游戲中,延遲的增加可能導致玩家的操作出現(xiàn)卡頓,無法及時響應游戲中的各種事件,嚴重影響游戲的流暢性和競技性;在視頻會議中,延遲則可能使畫面出現(xiàn)卡頓、聲音不同步等問題,讓溝通變得異常困難。
網(wǎng)絡丟包還會導致吞吐量的降低。吞吐量是指單位時間內(nèi)成功傳輸?shù)臄?shù)據(jù)量,它是衡量網(wǎng)絡性能的重要指標之一。當丟包發(fā)生時,一部分數(shù)據(jù)無法正常傳輸,這就必然會導致實際的吞吐量下降。對于一些大數(shù)據(jù)傳輸?shù)膱鼍?,如文件下載、數(shù)據(jù)備份等,吞吐量的降低會大大延長傳輸時間,降低工作效率。如果你需要從遠程服務器下載一個大型文件,原本預計幾個小時就能完成的下載任務,可能因為網(wǎng)絡丟包導致下載時間延長數(shù)倍,甚至可能因為丟包過于嚴重而導致下載失敗,需要重新開始。
對于基于 TCP 協(xié)議的應用來說,丟包更是意味著網(wǎng)絡擁塞和重傳。TCP 協(xié)議具有可靠性機制,當它檢測到數(shù)據(jù)包丟失時,會自動觸發(fā)重傳機制,以確保數(shù)據(jù)的完整性。然而,頻繁的重傳不僅會增加網(wǎng)絡流量,還會進一步加劇網(wǎng)絡擁塞,形成一種惡性循環(huán)。在高并發(fā)的網(wǎng)絡環(huán)境中,這種惡性循環(huán)可能會導致整個網(wǎng)絡的癱瘓,使所有依賴網(wǎng)絡的應用都無法正常運行。
網(wǎng)絡丟包對 Linux 系統(tǒng)性能的影響是多方面的,它不僅會降低用戶體驗,還會影響業(yè)務的正常運行,給企業(yè)帶來巨大的損失。因此,解決網(wǎng)絡丟包問題刻不容緩,這也是我們今天深入探討 Linux 性能優(yōu)化實戰(zhàn) —— 網(wǎng)絡丟包問題分析的重要原因。
在開始之前,我們先用一張圖解釋 linux 系統(tǒng)接收網(wǎng)絡報文的過程:
- 首先網(wǎng)絡報文通過物理網(wǎng)線發(fā)送到網(wǎng)卡
- 網(wǎng)絡驅(qū)動程序會把網(wǎng)絡中的報文讀出來放到 ring buffer 中,這個過程使用 DMA(Direct Memory Access),不需要 CPU 參與
- 內(nèi)核從 ring buffer 中讀取報文進行處理,執(zhí)行 IP 和 TCP/UDP 層的邏輯,最后把報文放到應用程序的 socket buffer 中
- 應用程序從 socket buffer 中讀取報文進行處理
圖片
二、探尋“兇手”:丟包可能發(fā)生在哪
當網(wǎng)絡丟包問題出現(xiàn)時,就如同一場懸疑案件,我們需要抽絲剝繭,從各個層面去探尋 “兇手”,也就是丟包發(fā)生的原因。在 Linux 系統(tǒng)中,丟包可能發(fā)生在網(wǎng)絡協(xié)議棧的各個層次,每個層次都有其獨特的丟包原因和排查方法。
2.1收包流程:數(shù)據(jù)包的 “入境之路”
圖片
當網(wǎng)卡接收到報文時,這場 “入境之旅” 就開啟了。首先,網(wǎng)卡通過 DMA(直接內(nèi)存訪問)技術,以極高的效率將數(shù)據(jù)包拷貝到 RingBuf(環(huán)形緩沖區(qū))中,就好比貨物被快速卸到了一個臨時倉庫。緊接著,網(wǎng)卡向 CPU 發(fā)起一個硬中斷,就像吹響了緊急集合哨,通知 CPU 有數(shù)據(jù)抵達 “國門”。
CPU 迅速響應,開始執(zhí)行對應的硬中斷處理例程,在這個例程里,它會將數(shù)據(jù)包的相關信息放入每 CPU 變量 poll_list 中,隨后觸發(fā)一個收包軟中斷,把后續(xù)的精細活兒交給軟中斷去處理。對應 CPU 的軟中斷線程 ksoftirqd 就登場了,它負責處理網(wǎng)絡包接收軟中斷,具體來說,就是執(zhí)行 net_rx_action () 函數(shù)。
在這個函數(shù)的 “指揮” 下,數(shù)據(jù)包從 RingBuf 中被小心翼翼地取出,然后進入?yún)f(xié)議棧,開啟層層闖關。從鏈路層開始,檢查報文合法性,剝?nèi)^、幀尾,接著進入網(wǎng)絡層,判斷包的走向,若是發(fā)往本機,再傳遞到傳輸層。最終,數(shù)據(jù)包被妥妥地放到 socket 的接收隊列中,等待應用層隨時來收取,至此,數(shù)據(jù)包算是順利 “入境”,完成了它的收包流程。
2.2發(fā)包流程:數(shù)據(jù)包的 “出境之旅”
圖片
應用程序要發(fā)送數(shù)據(jù)時,數(shù)據(jù)包的 “出境之旅” 便啟程了。首先,應用程序調(diào)用 Socket API(比如 sendmsg)發(fā)送網(wǎng)絡包,這一操作觸發(fā)系統(tǒng)調(diào)用,使得數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間,同時,內(nèi)核會為其分配一個 skb(sk_buff 結構體,它可是數(shù)據(jù)包在內(nèi)核中的 “代言人”,承載著各種關鍵信息),并將數(shù)據(jù)封裝其中。接著,skb 進入?yún)f(xié)議棧,開始自上而下的 “闖關升級”。
在傳輸層,會為數(shù)據(jù)添加 TCP 頭或 UDP 頭,進行擁塞控制、滑動窗口等一系列精細操作;到了網(wǎng)絡層,依據(jù)目標 IP 地址查找路由表,確定下一跳,填充 IP 頭中的源和目標 IP 地址、TTL 等關鍵信息,還可能進行 skb 切分,同時要經(jīng)過 netfilter 框架的 “安檢”,判斷是否符合過濾規(guī)則。
之后,在鄰居子系統(tǒng)填充目的 MAC 地址,再進入網(wǎng)絡設備子系統(tǒng),skb 被放入發(fā)送隊列 RingBuf 中,等待網(wǎng)卡發(fā)送。網(wǎng)卡發(fā)送完成后,會向 CPU 發(fā)出一個硬中斷,告知 “任務完成”,這個硬中斷又會觸發(fā)軟中斷,在軟中斷處理函數(shù)中,對 RingBuf 進行清理,把已經(jīng)發(fā)送成功的數(shù)據(jù)包殘留信息清除掉,就像清理運輸后的車廂,為下一次運輸做好準備,至此,數(shù)據(jù)包順利 “出境”,完成了它的發(fā)包流程。
三、鏈路層詳解
當鏈路層由于緩沖區(qū)溢出等原因?qū)е戮W(wǎng)卡丟包時,Linux 會在網(wǎng)卡收發(fā)數(shù)據(jù)的統(tǒng)計信息中記錄下收發(fā)錯誤的次數(shù)。鏈路層是網(wǎng)絡通信的基礎,它負責將網(wǎng)絡層傳來的數(shù)據(jù)封裝成幀,并通過物理介質(zhì)進行傳輸。鏈路層丟包通常是由于硬件故障、網(wǎng)絡擁塞或者配置錯誤等原因?qū)е碌摹.旀溌穼佑捎诰彌_區(qū)溢出等原因?qū)е戮W(wǎng)卡丟包時,Linux 會在網(wǎng)卡收發(fā)數(shù)據(jù)的統(tǒng)計信息中記錄下收發(fā)錯誤的次數(shù)。
我們可以通過 ethtool 或者 netstat 命令,來查看網(wǎng)卡的丟包記錄:
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 ,分別表示接收時的總包數(shù)、總錯誤數(shù)、進入 Ring Buffer 后因其他原因(如內(nèi)存不足)導致的丟包數(shù)以及 Ring Buffer 溢出導致的丟包數(shù)。
TX-OK、TX-ERR、TX-DRP、TX-OVR 也代表類似的含義,只不過是指發(fā)送時對應的各個指標。
這里我們沒有發(fā)現(xiàn)任何錯誤,說明虛擬網(wǎng)卡沒有丟包。不過要注意,如果用 tc 等工具配置了 QoS,那么 tc 規(guī)則導致的丟包,就不會包含在網(wǎng)卡的統(tǒng)計信息中。所以接下來,我們還要檢查一下 eth0 上是否配置了 tc 規(guī)則,并查看有沒有丟包。添加 -s 選項,以輸出統(tǒng)計信息:
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 上配置了一個網(wǎng)絡模擬排隊規(guī)則(qdisc netem),并且配置了丟包率為 30%(loss 30%)。再看后面的統(tǒng)計信息,發(fā)送了 8 個包,但是丟了 4個。看來應該就是這里導致 Nginx 回復的響應包被 netem 模塊給丟了。
既然發(fā)現(xiàn)了問題,解決方法也很簡單,直接刪掉 netem 模塊就可以了。執(zhí)行下面的命令,刪除 tc 中的 netem 模塊:
tc qdisc del dev eth0 root netem loss 30%
刪除后,重新執(zhí)行之前的 hping3 命令,看看現(xiàn)在還有沒有問題:
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。顯然,問題還是沒解決,丟包還在繼續(xù)發(fā)生。不過,既然鏈路層已經(jīng)排查完了,我們就繼續(xù)向上層分析,看看網(wǎng)絡層和傳輸層有沒有問題。
四、網(wǎng)絡層和傳輸層
在網(wǎng)絡層和傳輸層中,引發(fā)丟包的因素非常多。不過,其實想確認是否丟包,是非常簡單的事,因為 Linux 已經(jīng)為我們提供了各個協(xié)議的收發(fā)匯總情況。
⑴網(wǎng)絡層
網(wǎng)絡層負責將數(shù)據(jù)包從源地址傳輸?shù)侥康牡刂?,它主要處理路由選擇、IP 地址解析等功能。網(wǎng)絡層丟包可能是由于路由失敗、組包大小超過 MTU(最大傳輸單元)等原因引起的。當數(shù)據(jù)包的大小超過了網(wǎng)絡中某條鏈路的 MTU 時,數(shù)據(jù)包就需要被分片傳輸,如果分片過程出現(xiàn)問題,或者在重組過程中丟失了部分分片,就會導致丟包。我們可以通過netstat -s命令查看 IP 層的統(tǒng)計信息,其中IpInReceives表示接收到的 IP 數(shù)據(jù)包總數(shù),IpInDelivers表示成功交付給上層協(xié)議的 IP 數(shù)據(jù)包數(shù),如果兩者之間的差值較大,就可能存在網(wǎng)絡層丟包的情況。還可以查看IpOutNoRoutes指標,它表示因為找不到路由而丟棄的數(shù)據(jù)包數(shù),如果這個值不斷增加,說明可能存在路由問題導致丟包。
⑵傳輸層
傳輸層負責為應用層提供端到端的通信服務,常見的傳輸層協(xié)議有 TCP 和 UDP。傳輸層丟包可能是由于端口未監(jiān)聽、資源占用超過內(nèi)核限制等原因造成的。在高并發(fā)的網(wǎng)絡環(huán)境中,如果應用程序創(chuàng)建了大量的 TCP 連接,而系統(tǒng)資源(如文件描述符、內(nèi)存等)有限,就可能導致部分連接無法正常建立或維持,從而出現(xiàn)丟包現(xiàn)象。我們可以通過netstat -s命令查看 TCP 和 UDP 協(xié)議的統(tǒng)計信息,比如TcpRetransSegs表示 TCP 重傳的數(shù)據(jù)包數(shù),如果這個值較大,說明可能存在傳輸層丟包導致的重傳。UdpInErrors表示接收到的 UDP 錯誤數(shù)據(jù)包數(shù),如果該值不為零,也提示可能存在 UDP 丟包問題。
執(zhí)行 netstat -s 命令,可以看到協(xié)議的收發(fā)匯總,以及錯誤信息:
netstat -s
#輸出
Ip:
Forwarding: 1 //開啟轉(zhuǎn)發(fā)
31 total packets received //總收包數(shù)
0 forwarded //轉(zhuǎn)發(fā)包數(shù)
0 incoming packets discarded //接收丟包數(shù)
25 incoming packets delivered //接收的數(shù)據(jù)包數(shù)
15 requests sent out //發(fā)出的數(shù)據(jù)包數(shù)
Icmp:
0 ICMP messages received //收到的ICMP包數(shù)
0 input ICMP message failed //收到ICMP失敗數(shù)
ICMP input histogram:
0 ICMP messages sent //ICMP發(fā)送數(shù)
0 ICMP messages failed //ICMP失敗數(shù)
ICMP output histogram:
Tcp:
0 active connection openings //主動連接數(shù)
0 passive connection openings //被動連接數(shù)
11 failed connection attempts //失敗連接嘗試數(shù)
0 connection resets received //接收的連接重置數(shù)
0 connections established //建立連接數(shù)
25 segments received //已接收報文數(shù)
21 segments sent out //已發(fā)送報文數(shù)
4 segments retransmitted //重傳報文數(shù)
0 bad segments received //錯誤報文數(shù)
0 resets sent //發(fā)出的連接重置數(shù)
Udp:
0 packets received
...
TcpExt:
11 resets received for embryonic SYN_RECV sockets //半連接重置數(shù)
0 packet headers predicted
TCPTimeouts: 7 //超時數(shù)
TCPSynRetrans: 4 //SYN重傳數(shù)
...
etstat 匯總了 IP、ICMP、TCP、UDP 等各種協(xié)議的收發(fā)統(tǒng)計信息。不過,我們的目的是排查丟包問題,所以這里主要觀察的是錯誤數(shù)、丟包數(shù)以及重傳數(shù)??梢钥吹剑挥?TCP 協(xié)議發(fā)生了丟包和重傳,分別是:
- 11 次連接失敗重試(11 failed connection attempts)
- 4 次重傳(4 segments retransmitted)
- 11 次半連接重置(11 resets received for embryonic SYN_RECV sockets)
- 4 次 SYN 重傳(TCPSynRetrans)
- 7 次超時(TCPTimeouts)
這個結果告訴我們,TCP 協(xié)議有多次超時和失敗重試,并且主要錯誤是半連接重置。換句話說,主要的失敗,都是三次握手失敗。不過,雖然在這兒看到了這么多失敗,但具體失敗的根源還是無法確定。
五、iptables規(guī)則
iptables 是 Linux 系統(tǒng)中常用的防火墻工具,它可以根據(jù)用戶定義的規(guī)則對網(wǎng)絡數(shù)據(jù)包進行過濾和處理。如果 iptables 的規(guī)則配置不當,就可能導致數(shù)據(jù)包被錯誤地丟棄。我們可以通過iptables -L -n命令查看當前的 iptables 規(guī)則,檢查是否存在不合理的規(guī)則,如錯誤的端口過濾、源地址或目的地址限制等。也可以使用iptables -v -L命令查看規(guī)則的統(tǒng)計信息,了解哪些規(guī)則被頻繁匹配,從而判斷是否是 iptables 規(guī)則導致了丟包。
首先,除了網(wǎng)絡層和傳輸層的各種協(xié)議,iptables 和內(nèi)核的連接跟蹤機制也可能會導致丟包。所以,這也是發(fā)生丟包問題時我們必須要排查的一個因素。
先來看看連接跟蹤,要確認是不是連接跟蹤導致的問題,只需要對比當前的連接跟蹤數(shù)和最大連接跟蹤數(shù)即可。
# 主機終端中查詢內(nèi)核配置
$ sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 262144
$ sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 182
可以看到,連接跟蹤數(shù)只有 182,而最大連接跟蹤數(shù)則是 262144。顯然,這里的丟包,不可能是連接跟蹤導致的。
接著,再來看 iptables?;仡櫼幌?iptables 的原理,它基于 Netfilter 框架,通過一系列的規(guī)則,對網(wǎng)絡數(shù)據(jù)包進行過濾(如防火墻)和修改(如 NAT)。這些 iptables 規(guī)則,統(tǒng)一管理在一系列的表中,包括 filter、nat、mangle(用于修改分組數(shù)據(jù)) 和 raw(用于原始數(shù)據(jù)包)等。而每張表又可以包括一系列的鏈,用于對 iptables 規(guī)則進行分組管理。
對于丟包問題來說,最大的可能就是被 filter 表中的規(guī)則給丟棄了。要弄清楚這一點,就需要我們確認,那些目標為 DROP 和 REJECT 等會棄包的規(guī)則,有沒有被執(zhí)行到??梢灾苯硬樵?DROP 和 REJECT 等規(guī)則的統(tǒng)計信息,看看是否為0。如果不是 0 ,再把相關的規(guī)則拎出來進行分析。
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 規(guī)則的統(tǒng)計數(shù)值不是 0,它們分別在INPUT 和 OUTPUT 鏈中。這兩條規(guī)則實際上是一樣的,指的是使用 statistic 模塊,進行隨機 30% 的丟包。0.0.0.0/0 表示匹配所有的源 IP 和目的 IP,也就是會對所有包都進行隨機 30% 的丟包??雌饋恚@應該就是導致部分丟包的“罪魁禍首”了。
執(zhí)行下面的兩條 iptables 命令,刪除這兩條 DROP 規(guī)則。
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
再次執(zhí)行剛才的 hping3 命令,看看現(xiàn)在是否正常
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
這次輸出你可以看到,現(xiàn)在已經(jīng)沒有丟包了,并且延遲的波動變化也很小??磥?,丟包問題應該已經(jīng)解決了。
不過,到目前為止,我們一直使用的 hping3 工具,只能驗證案例 Nginx 的 80 端口處于正常監(jiān)聽狀態(tài),卻還沒有訪問 Nginx 的 HTTP 服務。所以,不要匆忙下結論結束這次優(yōu)化,我們還需要進一步確認,Nginx 能不能正常響應 HTTP 請求。我們繼續(xù)在終端二中,執(zhí)行如下的 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 端口是正常狀態(tài),為什么還是不能正常響應 HTTP 請求呢?別忘了,我們還有個大殺器——抓包操作??磥碛斜匾グ纯戳?。
六、tcpdump命令工具使用
執(zhí)行下面的 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
然后,切換到終端二中,再次執(zhí)行前面的 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)發(fā)送過來的 FIN 包,說明客戶端的連接關閉了
根據(jù) curl 設置的 3 秒超時選項,你應該能猜到,這是因為 curl 命令超時后退出了。用 Wireshark 的 Flow Graph 來表示,你可以更清楚地看到上面這個問題:
圖片
這里比較奇怪的是,我們并沒有抓取到 curl 發(fā)來的 HTTP GET 請求。那究竟是網(wǎng)卡丟包了,還是客戶端就沒發(fā)過來呢?
可以重新執(zhí)行 netstat -i 命令,確認一下網(wǎng)卡有沒有丟包問題:
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 的輸出中,你可以看到,接收丟包數(shù)(RX-DRP)是 344,果然是在網(wǎng)卡接收時丟包了。不過問題也來了,為什么剛才用 hping3 時不丟包,現(xiàn)在換成 GET 就收不到了呢?還是那句話,遇到搞不懂的現(xiàn)象,不妨先去查查工具和方法的原理。我們可以對比一下這兩個工具:
- hping3 實際上只發(fā)送了 SYN 包;
- curl 在發(fā)送 SYN 包后,還會發(fā)送 HTTP GET 請求。HTTP GET本質(zhì)上也是一個 TCP 包,但跟 SYN 包相比,它還攜帶了 HTTP GET 的數(shù)據(jù)。
通過這個對比,你應該想到了,這可能是 MTU 配置錯誤導致的。為什么呢?
其實,仔細觀察上面 netstat 的輸出界面,第二列正是每個網(wǎng)卡的 MTU 值。eth0 的 MTU只有 100,而以太網(wǎng)的 MTU 默認值是 1500,這個 100 就顯得太小了。當然,MTU 問題是很好解決的,把它改成 1500 就可以了。
ifconfig eth0 mtu 1500
修改完成后,再切換到終端二中,再次執(zhí)行 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 響應,說明丟包的問題終于徹底解決了。
七、實戰(zhàn)演練:排查與解決 Nginx 丟包問題
理論上的分析固然重要,但實際操作才是檢驗真理的關鍵。下面,我們將通過一個具體的案例,以 Nginx 應用為例,深入探討如何在實際場景中排查和解決網(wǎng)絡丟包問題。
7.1模擬訪問與初步判斷
假設我們在一臺 Linux 服務器上部署了 Nginx 應用,現(xiàn)在懷疑它存在網(wǎng)絡丟包問題。我們首先使用 hping3 命令來模擬訪問 Nginx 服務。hping3 是一個功能強大的網(wǎng)絡工具,它可以發(fā)送各種類型的網(wǎng)絡數(shù)據(jù)包,幫助我們測試網(wǎng)絡的連通性和性能。執(zhí)行以下命令:
hping3 -c 10 -S -p 80 192.168.0.30
在這個命令中,-c 10表示發(fā)送 10 個請求包,-S表示使用 TCP SYN 標志位,-p 80指定目標端口為 80,即 Nginx 服務默認的端口,192.168.0.30是 Nginx 服務器的 IP 地址。執(zhí)行命令后,我們得到如下輸出:
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
從輸出結果中,我們可以清晰地看到,總共發(fā)送了 10 個請求包,但只收到了 5 個回復包,丟包率高達 50%。而且,每個請求的 RTT(往返時間)波動非常大,最小值只有 3.0ms,而最大值卻達到了 3027.2ms,這表明網(wǎng)絡中很可能存在丟包現(xiàn)象。
7.2鏈路層排查
初步判斷存在丟包問題后,我們首先從鏈路層開始排查。使用netstat -i命令查看虛擬網(wǎng)卡的丟包記錄:
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表示接收時的總包數(shù),RX-ERR表示總錯誤數(shù),RX-DRP表示進入 Ring Buffer 后因其他原因(如內(nèi)存不足)導致的丟包數(shù),RX-OVR表示 Ring Buffer 溢出導致的丟包數(shù),TX-OK至TX-OVR則表示發(fā)送時的相應指標。從這里可以看出,虛擬網(wǎng)卡的各項錯誤指標均為 0,說明虛擬網(wǎng)卡本身沒有丟包。
不過,如果使用tc等工具配置了 QoS(Quality of Service,服務質(zhì)量),tc規(guī)則導致的丟包不會包含在網(wǎng)卡的統(tǒng)計信息中。因此,我們還需要檢查eth0上是否配置了tc規(guī)則,并查看是否有丟包。添加-s選項以輸出統(tǒng)計信息:
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上配置了一個網(wǎng)絡模擬排隊規(guī)則qdisc netem,并且設置了丟包率為 30%(loss 30%)。從后面的統(tǒng)計信息可知,發(fā)送了 8 個包,但丟了 4 個。這很可能就是導致 Nginx 回復的響應包被netem模塊丟棄的原因。既然找到了問題,解決方法就很簡單,直接刪除netem模塊:
tc qdisc del dev eth0 root netem loss 30%
刪除后,重新執(zhí)行hping3命令,看看問題是否解決。然而,從hping3的輸出中發(fā)現(xiàn),仍然有 50% 的丟包,RTT 波動依舊很大,說明問題還未得到解決,需要繼續(xù)向上層排查。
網(wǎng)絡層和傳輸層排查
接下來,我們排查網(wǎng)絡層和傳輸層。在這兩層中,引發(fā)丟包的因素眾多,但確認是否丟包卻相對簡單,因為 Linux 已經(jīng)為我們提供了各個協(xié)議的收發(fā)匯總情況。執(zhí)行netstat -s命令,查看 IP、ICMP、TCP 和 UDP 等協(xié)議的收發(fā)統(tǒng)計信息:
netstat -s
輸出結果非常豐富,這里我們重點關注與丟包相關的信息。例如,從 TCP 協(xié)議的統(tǒng)計信息中,我們看到有多次超時和失敗重試,并且主要錯誤是半連接重置,這表明可能存在三次握手失敗的問題。這可能是由于網(wǎng)絡擁塞、端口被占用、防火墻限制等原因?qū)е碌?。此時,我們需要進一步分析具體的錯誤原因,可以結合其他工具和命令,如lsof查看端口占用情況,檢查防火墻規(guī)則等。
7.3iptables 排查
iptables 是 Linux 系統(tǒng)中常用的防火墻工具,其規(guī)則配置不當可能導致數(shù)據(jù)包被丟棄。首先,我們檢查內(nèi)核的連接跟蹤機制,查看當前的連接跟蹤數(shù)和最大連接跟蹤數(shù):
cat /proc/sys/net/nf_conntrack_count
cat /proc/sys/net/nf_conntrack_max
假設連接跟蹤數(shù)只有 182,而最大連接跟蹤數(shù)是 262144,說明連接跟蹤數(shù)沒有達到上限,不存在因連接跟蹤數(shù)滿而導致丟包的問題。
接著,查看 iptables 規(guī)則,使用iptables -L -n命令:
iptables -L -n
在輸出的規(guī)則列表中,我們發(fā)現(xiàn)有兩條DROP規(guī)則,使用了statistic模塊進行隨機 30% 的丟包。這顯然是導致丟包的一個重要原因。我們將這兩條規(guī)則直接刪除,然后重新執(zhí)行hping3命令。此時,hping3的輸出顯示已經(jīng)沒有丟包,這說明 iptables 的錯誤規(guī)則是導致之前丟包的原因之一。
端口狀態(tài)檢查與進一步排查
雖然hping3驗證了 Nginx 的 80 端口處于正常監(jiān)聽狀態(tài),但還需要檢查 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/
結果發(fā)現(xiàn)連接超時,這表明雖然端口監(jiān)聽正常,但 Nginx 在處理 HTTP 請求時可能存在問題。為了進一步分析,我們使用tcpdump命令抓包:
tcpdump -i eth0 -n tcp port 80
在另一個終端執(zhí)行curl命令,然后查看tcpdump的輸出。發(fā)現(xiàn)前三個包是正常的 TCP 三次握手,但第四個包卻是在 3 秒后才收到,并且是客戶端發(fā)送過來的 FIN 包,這說明客戶端的連接已經(jīng)關閉。
重新執(zhí)行netstat -i命令,檢查網(wǎng)卡是否有丟包,發(fā)現(xiàn)果然是在網(wǎng)卡接收時丟包了。進一步檢查最大傳輸單元 MTU(Maximum Transmission Unit):
ifconfig eth0 | grep MTU
發(fā)現(xiàn)eth0的 MTU 只有 100,而以太網(wǎng)的 MTU 默認值是 1500。MTU 過小可能導致數(shù)據(jù)包在傳輸過程中需要分片,從而增加丟包的風險。我們將 MTU 修改為 1500:
ifconfig eth0 mtu 1500
再次執(zhí)行curl命令,問題得到解決,Nginx 能夠正常響應 HTTP 請求。