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

1、上節回顧

上一節,咱們梳理了,應用程序容器化後性能降低的分析方法。一塊兒先簡單回顧下。
容器利用 Linux 內核提供的命名空間技術,將不一樣應用程序的運行隔離起來,並用統一的鏡像,來管理應用程序的依賴環境。這爲應用程序的管理和維護,帶來了極大的便捷性,並進一步催生
了微服務、雲原生等新一代技術架構。nginx

不過,雖然說有不少優點,但容器化也會對應用程序的性能帶來必定影響。好比,上一節咱們一塊兒分析的 Java 應用,就容易發生啓動過慢、運行一段時間後 OOM 退出等問題。當你碰到這種問
題時,不要慌,咱們前面四大基礎模塊中的各類思路,都依然適用。docker

實際上,咱們專欄中的不少案例都在容器中運行。容器化後,應用程序會經過命名空間進行隔離。因此,你在分析時,不要忘告終合命名空間、cgroups、iptables 等來綜合分析。好比:bash

  • cgroups 會影響容器應用的運行;
  • iptables 中的 NAT,會影響容器的網絡性能;
  • 疊加文件系統,會影響應用的 I/O 性能等。

關於 NAT 的影響,我在網絡模塊的 如何優化 NAT 性能 文章中,已經爲你介紹了不少優化思路。今天,咱們一塊兒來看另外一種狀況,也就是丟包的分析方法。服務器

所謂丟包,是指在網絡數據的收發過程當中,因爲種種緣由,數據包還沒傳輸到應用程序中,就被丟棄了。這些被丟棄包的數量,除以總的傳輸包數,也就是咱們常說的丟包率。丟包率是網絡性
能中最核心的指標之一。網絡

丟包一般會帶來嚴重的性能降低,特別是對 TCP 來講,丟包一般意味着網絡擁塞和重傳,進而還會致使網絡延遲增大、吞吐下降。架構

接下來,我就以最經常使用的反向代理服務器 Nginx 爲例,帶你一塊兒看看,如何分析網絡丟包的問題。因爲內容比較多,這個案例將分爲上下兩篇來說解,今天咱們先看第一部份內容。curl

2、案例準備

今天的案例須要用到兩臺虛擬機,仍是基於 Ubuntu 18.04,一樣適用於其餘的 Linux 系統。我使用的案例環境以下所示:socket

  • 機器配置:2 CPU,8GB 內存。
  • 預先安裝 docker、curl、hping3 等工具,如 apt install docker.io curl hping3。

這些工具,咱們在前面的案例中已經屢次使用,這裏就再也不重複介紹。
如今,打開兩個終端,分別登陸到這兩臺虛擬機中,並安裝上述工具。
注意,如下全部命令都默認以 root 用戶運行,若是你用普通用戶身份登錄系統,請運行 sudosu root 命令,切換到 root 用戶。tcp

若是安裝過程有問題,你能夠先上網搜索解決,實在解決不了的,記得在留言區向我提問。微服務

到這裏,準備工做就完成了。接下來,咱們正式進入操做環節。

3、案例分析

咱們今天要分析的案例是一個 Nginx 應用,以下圖所示,hping3 和 curl 是 Nginx 的客戶端。

爲了方便你運行,我已經把它打包成了一個 Docker 鏡像,並推送到 Docker Hub 中。你能夠直接按照下面的步驟來運行它。

在終端一中執行下面的命令,啓動 Nginx 應用,並在 80 端口監聽。若是一切正常,你應該能夠看到以下的輸出:

docker run --name nginx --hostname nginx --privileged -p 80:80 -itd feisky/nginx:drop
dae0202cc27e5082b282a6aeeb1398fcec423c642e63322da2a97b9ebd7538e

而後,執行 docker ps 命令,查詢容器的狀態,你會發現容器已經處於運行狀態(Up)了:

docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                NAMES
dae0202cc27e        feisky/nginx:drop   "/start.sh"         4 minutes ago       Up 4 minutes        0.0.0.0:80->80/tcp   nginx

不過,從 docker ps 的輸出,咱們只能知道容器處於運行狀態,至於 Nginx 是否能夠正常處理外部請求,還須要進一步的確認。

接着,咱們切換到終端二中,執行下面的 hping3 命令,進一步驗證 Nginx 是否是真的能夠正常訪問了。注意,這裏我沒有使用 ping,是由於 ping 基於 ICMP 協議,而 Nginx 使用的是 TCP協議。

# -c 表示發送 10 個請求,-S 表示使用 TCP SYN,-p 指定端口爲 80
$ 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=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

實際測試代碼以下:

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=4 win=65535 rtt=6.7 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=2 win=65535 rtt=3095.3 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=6 win=65535 rtt=2.9 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=9 win=65535 rtt=6.8 ms

--- 192.168.118.85 hping statistic ---
10 packets transmitted, 4 packets received, 60% packet loss
round-trip min/avg/max = 2.9/777.9/3095.3 m

從 hping3 的輸出中,咱們能夠發現,發送了 10 個請求包,卻只收到了 5 個回覆,50% 的包都丟了。再觀察每一個請求的 RTT 能夠發現,RTT 也有很是大的波動變化,小的時候只有 3ms,而
大的時候則有 3s。

根據這些輸出,咱們基本能判斷,已經發生了丟包現象。能夠猜想,3s 的 RTT ,極可能是由於丟包後重傳致使的。那究竟是哪裏發生了丟包呢?

排查以前,咱們能夠回憶一下 Linux 的網絡收發流程,先從理論上分析,哪裏有可能會發生丟包。你不妨拿出手邊的筆和紙,邊回憶邊在紙上梳理,思考清楚再繼續下面的內容。
在這裏,爲了幫你理解網絡丟包的原理,我畫了一張圖,你能夠保存並打印出來使用:

從圖中你能夠看出,可能發生丟包的位置,實際上貫穿了整個網絡協議棧。換句話說,全程都有丟包的可能。好比咱們從下往上看:

  • 在兩臺 VM 鏈接之間,可能會發生傳輸失敗的錯誤,好比網絡擁塞、線路錯誤等;
  • 在網卡收包後,環形緩衝區可能會由於溢出而丟包;
  • 在鏈路層,可能會由於網絡幀校驗失敗、QoS 等而丟包;
  • 在 IP 層,可能會由於路由失敗、組包大小超過 MTU 等而丟包;
  • 在傳輸層,可能會由於端口未監聽、資源佔用超過內核限制等而丟包;
  • 在套接字層,可能會由於套接字緩衝區溢出而丟包;
  • 在應用層,可能會由於應用程序異常而丟包;

此外,若是配置了 iptables 規則,這些網絡包也可能由於 iptables 過濾規則而丟包。

固然,上面這些問題,還有可能同時發生在通訊的兩臺機器中。不過,因爲咱們沒對 VM2 作任何修改,而且 VM2 也只運行了一個最簡單的 hping3 命令,這兒不妨假設它是沒有問題的。
爲了簡化整個排查過程,咱們還能夠進一步假設, VM1 的網絡和內核配置也沒問題。這樣一來,有可能發生問題的位置,就都在容器內部了。

如今咱們切換回終端一,執行下面的命令,進入容器的終端中:

docker exec -it nginx bash
root@nginx:/#

在這裏簡單說明一下,接下來的全部分析,前面帶有 root@nginx:/# 的操做,都表示在容器中進行。

那麼, 接下來,咱們就能夠從協議棧中,逐層排查丟包問題。

4、鏈路層

首先,來看最底下的鏈路層。當緩衝區溢出等緣由致使網卡丟包時,Linux 會在網卡收發數據的統計信息中,記錄下收發錯誤的次數。

你能夠經過 ethtool 或者 netstat ,來查看網卡的丟包記錄。好比,能夠在容器中執行下面的命令,查看丟包狀況:

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       31      0      0 0             8      0      0      0 BMRU
lo       65536        0      0      0 0             0      0      0      0 LRU

實際測試代碼以下:

[root@luoahong ~]# netstat -i
Kernel Interface table
Iface             MTU    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
br-ad2616372f01  1500        6      0      0 0            27      0      0      0 BMU
docker0          1500        6      0      0 0            27      0      0      0 BMRU
eth0             1500      532      0      0 0           292      0      0      0 BMRU
lo              65536        0      0      0 0             0      0      0      0 LRU
veth5a876cb      1500        6      0      0 0            38      0      0      0 BM

輸出中的 RX-OK、RX-ERR、RX-DRP、RX-OVR ,分別表示接收時的總包數、總錯誤數、進入Ring Buffer 後因其餘緣由(如內存不足)致使的丟包數以及 Ring Buffer 溢出致使的丟包數。

TX-OK、TX-ERR、TX-DRP、TX-OVR 也表明相似的含義,只不過是指發送時對應的各個指標。

注意,因爲 Docker 容器的虛擬網卡,其實是一對 veth pair,一端接入容器中用做 eth0,另外一端在主機中接入 docker0 網橋中。veth 驅動並無實現網絡統
計的功能,因此使用 ethtool -S 命令,沒法獲得網卡收發數據的彙總信息。

從這個輸出中,咱們沒有發現任何錯誤,說明容器的虛擬網卡沒有丟包。不過要注意,若是用 tc
等工具配置了 QoS,那麼 tc 規則致使的丟包,就不會包含在網卡的統計信息中。

因此接下來,咱們還要檢查一下 eth0 上是否配置了 tc 規則,並查看有沒有丟包。咱們繼續容器終端中,執行下面的 tc 命令,不過此次注意添加 -s 選項,以輸出統計信息:

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

實際測試代碼:

[root@luoahong ~]# tc -s qdisc show dev eth0
qdisc pfifo_fast 0: root refcnt 2 bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
 Sent 35109 bytes 304 pkt (dropped 0, overlimits 0 requeues 0) 
 backlog 0b 0p requeues 0

從 tc 的輸出中能夠看到, eth0 上面配置了一個網絡模擬排隊規則(qdisc netem),而且配置了丟包率爲 30%(loss 30%)。再看後面的統計信息,發送了 8 個包,可是丟了 4 個。

看來,應該就是這裏,致使 Nginx 回覆的響應包,被 netem 模塊給丟了。
既然發現了問題,解決方法也就很簡單了,直接刪掉 netem 模塊就能夠了。咱們能夠繼續在容
器終端中,執行下面的命令,刪除 tc 中的 netem 模塊:

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

顯然,問題仍是沒解決,丟包還在繼續發生。不過,既然鏈路層已經排查完了,咱們就繼續向上層分析,看看網絡層和傳輸層有沒有問題。

5、網絡層和傳輸層

咱們知道,在網絡層和傳輸層中,引起丟包的因素很是多。不過,其實想確認是否丟包,是很是簡單的事,由於 Linux 已經爲咱們提供了各個協議的收發彙總狀況。

咱們繼續在容器終端中,執行下面的 netstat -s 命令,就能夠看到協議的收發彙總,以及錯誤信息了:

root@nginx:/# 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 重傳數
	...

實際測試代碼:

root@luoahong:~# netstat -s
Ip:
    Forwarding: 2
    478 total packets received
    4 with invalid addresses
    0 forwarded
    0 incoming packets discarded
    474 incoming packets delivered
    329 requests sent out
    20 outgoing packets dropped
Icmp:
    40 ICMP messages received
    0 input ICMP message failed
    ICMP input histogram:
        destination unreachable: 40
    42 ICMP messages sent
    0 ICMP messages failed
    ICMP output histogram:
        destination unreachable: 42
IcmpMsg:
        InType3: 40
        OutType3: 42
Tcp:
    2 active connection openings
    1 passive connection openings
    0 failed connection attempts
    0 connection resets received
    1 connections established
    266 segments received
    172 segments sent out
    2 segments retransmitted
    0 bad segments received
    11 resets sent
Udp:
    52 packets received
    42 packets to unknown port received
    0 packet receive errors
    93 packets sent
    0 receive buffer errors
    0 send buffer errors
    IgnoredMulti: 73
UdpLite:
TcpExt:
    3 delayed acks sent
    66 packet headers predicted
    20 acknowledgments not containing data payload received
    30 predicted acknowledgments
    TCPTimeouts: 1
    TCPLossProbes: 1
    TCPRcvCoalesce: 145
    TCPOFOQueue: 4
    TCPOrigDataSent: 68
    TCPKeepAlive: 2
IpExt:
    InBcastPkts: 73
    InOctets: 530734
    OutOctets: 28599
    InBcastOctets: 10601
    InNoECTPkts: 657

netstat 彙總了 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 協議有屢次超時和失敗重試,而且主要錯誤是半鏈接重置。換句話說,主要的失敗,都是三次握手失敗。

不過,雖然在這兒看到了這麼多失敗,但具體失敗的根源仍是沒法肯定。因此,咱們還須要繼續順着協議棧來分析。接下來的幾層又該如何分析呢?你不妨本身先來思考操做一下,下一節咱們
繼續來一塊兒探討。

6、小結

網絡丟包,一般會帶來嚴重的性能降低,特別是對 TCP 來講,丟包一般意味着網絡擁塞和重傳,進一步還會致使網絡延遲增大、吞吐下降。

今天的這個案例,咱們學會了如何從鏈路層、網絡層和傳輸層等入手,分析網絡丟包的問題。不過,案例最後,咱們尚未找出最終的性能瓶頸,下一節,我將繼續爲你講解。

相關文章
相關標籤/搜索