上一節,咱們一塊兒學習瞭如何分析網絡丟包的問題,特別是從鏈路層、網絡層以及傳輸層等主要的協議棧中進行分析。html
不過,經過前面這幾層的分析,咱們仍是沒有找出最終的性能瓶頸。看來,仍是要繼續深挖才能夠。今天,咱們就來繼續分析這個未果的案例。nginx
在開始下面的內容前,你能夠先回憶一下上節課的內容,而且本身動腦想想,除了咱們提到的鏈路層、網絡層以及傳輸層以外,還有哪些潛在問題可能會致使丟包呢?web
首先咱們要知道,除了網絡層和傳輸層的各類協議,iptables 和內核的鏈接跟蹤機制也可能會致使丟包。因此,這也是發生丟包問題時,咱們必需要排查的一個因素。docker
咱們先來看看鏈接跟蹤,我已經在 如何優化 NAT 性能 文章中,給你講過鏈接跟蹤的優化思路。要確認是否是鏈接跟蹤致使的問題,其實只須要對比當前的鏈接跟蹤數和最大鏈接跟蹤數便可。bash
不過,因爲鏈接跟蹤在 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
實際測試代碼以下:dom
[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。顯然,這裏的丟包,不多是鏈接跟蹤致使的。curl
接着,再來看 iptables。回顧一下 iptables 的原理,它基於 Netfilter 框架,經過一系列的規則,對網絡數據包進行過濾(如防火牆)和修改(如 NAT)。tcp
這些 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 命令,抓取 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 的輸出中,咱們就能夠看到:
我想,根據 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 就收不到了呢?
仍是那句話,遇到搞不懂的現象,不妨先去查查工具和方法的原理。咱們能夠對比一下這兩個工具:
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 協議棧的原理來逐層分析。