Linux性能優化實戰學習筆記:第四十八講


一、上節回顧

上一節,我們一起學習了如何分析網絡丟包的問題,特別是從鏈路層、網絡層以及傳輸層等主要的協議棧中進行分析。

不過,通過前面這幾層的分析,我們還是沒有找出最終的性能瓶頸。看來,還是要繼續深挖才可以。今天,我們就來繼續分析這個未果的案例。

在開始下面的內容前,你可以先回憶一下上節課的內容,並且自己動腦想一想,除了我們提到的鏈路層、網絡層以及傳輸層之外,還有哪些潛在問題可能會導致丟包呢?

二、iptables

首先我們要知道,除了網絡層和傳輸層的各種協議,iptables 和內核的連接跟蹤機制也可能會導致丟包。所以,這也是發生丟包問題時,我們必須要排查的一個因素。

我們先來看看連接跟蹤,我已經在 如何優化 NAT 性能 文章中,給你講過連接跟蹤的優化思路。要確認是不是連接跟蹤導致的問題,其實只需要對比當前的連接跟蹤數和最大連接跟蹤數即可。

不過,由於連接跟蹤在 Linux 內核中是全局的(不屬於網絡命名空間),我們需要退出容器終端,回到主機中來查看。

你可以在容器終端中,執行 exit ;然后執行下面的命令,查看連接跟蹤數:

# 容器終端中執行 exit
root@nginx:/# exit
exit

# 主機終端中查詢內核配置
$ sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 262144
$ sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 182

實際測試代碼如下

[root@luoahong ~]# sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 262144
[root@luoahong ~]# sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 11

從這兒你可以看到,連接跟蹤數只有 182,而最大連接跟蹤數則是 262144。顯然,這里的丟包,不可能是連接跟蹤導致的。

接着,再來看 iptables。回顧一下 iptables 的原理,它基於 Netfilter 框架,通過一系列的規則,對網絡數據包進行過濾(如防火牆)和修改(如 NAT)。

這些 iptables 規則,統一管理在一系列的表中,包括 filter(用於過濾)、nat(用於 NAT)、mangle(用於修改分組數據) 和 raw(用於原始數據包)等。而每張表又可以包括一系列的
鏈,用於對 iptables 規則進行分組管理。

對於丟包問題來說,最大的可能就是被 filter 表中的規則給丟棄了。要弄清楚這一點,就需要我們確認,那些目標為 DROP 和 REJECT 等會棄包的規則,有沒有被執行到。

你可以把所有的 iptables 規則列出來,根據收發包的特點,跟 iptables 規則進行匹配。不過顯然,如果 iptables 規則比較多,這樣做的效率就會很低。

當然,更簡單的方法,就是直接查詢 DROP 和 REJECT 等規則的統計信息,看看是否為 0。如果統計值不是 0 ,再把相關的規則拎出來進行分析。

我們可以通過 iptables -nvL 命令,查看各條規則的統計信息。比如,你可以執行下面的 dockerexec 命令,進入容器終端;然后再執行下面的 iptables 命令,就可以看到 filter 表的統計數據了:

# 在主機中執行
$ docker exec -it nginx bash

# 在容器中執行
root@nginx:/# 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

實際測試代碼如下

root@nginx:/# iptables -t filter -nvL
Chain INPUT (policy ACCEPT 24 packets, 960 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    7   280 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 23 packets, 1012 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    8   352 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.29999999981
root@nginx:/# 

從 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

實際測試代碼如下:

root@luoahong:~# hping3 -c 10 -S -p 80 192.168.118.85
HPING 192.168.118.85 (ens33 192.168.118.85): S set, 40 headers + 0 data bytes
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=1 win=65535 rtt=3.9 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=65535 rtt=1013.3 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=4 win=65535 rtt=8.7 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=3 win=65535 rtt=1017.5 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=65535 rtt=7.3 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=7 win=65535 rtt=5.0 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=8 win=65535 rtt=10.8 ms

--- 192.168.118.85 hping statistic ---
10 packets transmitted, 7 packets received, 30% packet loss
round-trip min/avg/max = 3.9/295.2/1017.5 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

實際測試代碼如下:

root@luoahong:~# curl --max-time 3 http://192.168.118.85
curl: (28) Operation timed out after 3002 milliseconds with 0 bytes received

從 curl 的輸出中,你可以發現,這次連接超時了。可是,剛才我們明明用 hping3 驗證了端口正常,現在卻發現 HTTP 連接超時,是不是因為 Nginx 突然異常退出了呢?

不妨再次運行 hping3 來確認一下:

hping3 -c 3 -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.8 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=1 win=5120 rtt=7.7 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=2 win=5120 rtt=3.6 ms

--- 192.168.0.30 hping statistic ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 3.6/6.4/7.8 ms

實際測試代碼如下:

root@luoahong:~# hping3 -c 10 -S -p 80 192.168.118.85
HPING 192.168.118.85 (ens33 192.168.118.85): S set, 40 headers + 0 data bytes
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=1 win=65535 rtt=3.9 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=65535 rtt=1013.3 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=4 win=65535 rtt=8.7 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=3 win=65535 rtt=1017.5 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=65535 rtt=7.3 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=7 win=65535 rtt=5.0 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=8 win=65535 rtt=10.8 ms

--- 192.168.118.85 hping statistic ---
10 packets transmitted, 7 packets received, 30% packet loss
round-trip min/avg/max = 3.9/295.2/1017.5 ms

奇怪,hping3 的結果顯示,Nginx 的 80 端口確確實實還是正常狀態。這該如何是好呢?別忘了,我們還有個大殺器——抓包操作。看來有必要抓包看看了。

三、tcpdump

接下來,我們切換回終端一,在容器終端中,執行下面的 tcpdump 命令,抓取 80 端口的包:

root@nginx:/# 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

實際測試代碼如下:

root@luoahong:~# curl --max-time 3 http://192.168.118.85
curl: (28) Operation timed out after 3002 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

實際測試代碼如下:

root@nginx:/# 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
08:48:31.746239 IP 192.168.118.77.47274 > 172.17.0.2.80: Flags [S], seq 3419504692, win 29200, options [mss 1460,sackOK,TS val 109552116 ecr 0,nop,wscale 7], length 0
08:48:31.746400 IP 172.17.0.2.80 > 192.168.118.77.47274: Flags [S.], seq 1891132195, ack 3419504693, win 65392, options [mss 256,sackOK,TS val 2205929180 ecr 109552116,nop,wscale 7], length 0
08:48:31.747134 IP 192.168.118.77.47274 > 172.17.0.2.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 109552117 ecr 2205929180], length 0
08:48:34.748411 IP 192.168.118.77.47274 > 172.17.0.2.80: Flags [F.], seq 79, ack 1, win 229, options [nop,nop,TS val 109555119 ecr 2205929180], length 0
08:48:34.748567 IP 172.17.0.2.80 > 192.168.118.77.47274: Flags [.], ack 1, win 511, options [nop,nop,TS val 2205932182 ecr 109552117,nop,nop,sack 1 {79:80}], length 0
^C
5 packets captured
5 packets received by filter
0 packets dropped by kernel
22 packets dropped by interface

經過這么一系列的操作,從 tcpdump 的輸出中,我們就可以看到:

  • 前三個包是正常的 TCP 三次握手,這沒問題;
  • 但第四個包卻是在 3 秒以后了,並且還是客戶端(VM2)發送過來的 FIN 包,也就說明,客戶端的連接關閉了。

我想,根據 curl 設置的 3 秒超時選項,你應該能猜到,這是因為 curl 命令超時后退出了。我把這一過程,用 TCP 交互的流程圖(實際上來自 Wireshark 的 Flow Graph)來表示,你可
以更清楚地看到上面這個問題:

這里比較奇怪的是,我們並沒有抓取到 curl 發來的 HTTP GET 請求。那么,究竟是網卡丟包了,還是客戶端壓根兒就沒發過來呢?

我們可以重新執行 netstat -i 命令,確認一下網卡有沒有丟包問題:

root@nginx:/# 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 就可以了。我們繼續在容器終端中,執行下面的命令,把容器 eth0 的 MTU 改成 1500:

root@nginx:/# 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>

實際測試代碼如下:

root@luoahong:~# curl --max-time 3 http://192.168.118.85
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

非常不容易呀,這次終於看到了熟悉的 Nginx 響應,說明丟包的問題終於徹底解決了。

當然,案例結束前,不要忘記停止今天的 Nginx 應用。你可以切換回終端一,在容器終端中執行exit 命令,退出容器終端:

root@nginx:/# exit
exit

最后,再執行下面的 docker 命令,停止並刪除 Nginx 容器:

docker rm -f nginx

四、小結

今天,我繼續帶你分析了網絡丟包的問題。特別是在時不時丟包的情況下,定位和優化都需要我們花心思重點投入。

網絡丟包問題的嚴重性不言而喻。碰到丟包問題時,我們還是要從 Linux 網絡收發的流程入手,結合 TCP/IP 協議棧的原理來逐層分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM