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

1、上節回顧

上一節,咱們學習了碰到分佈式拒絕服務(DDoS)的緩解方法。簡單回顧一下,DDoS利用大量的僞造請求,致使目標服務要耗費大量資源,來處理這些無效請求,進而沒法正
常響應正經常使用戶的請求。html

因爲 DDoS 的分佈式、大流量、難追蹤等特色,目前確實尚未方法,可以徹底防護DDoS 帶來的問題,咱們只能設法緩解 DDoS 帶來的影響。node

好比,你能夠購買專業的流量清洗設備和網絡防火牆,在網絡入口處阻斷惡意流量,只保留正常流量進入數據中心的服務器。nginx

在 Linux 服務器中,你能夠經過內核調優、DPDK、XDP 等多種方法,增大服務器的抗攻擊能力,下降 DDoS 對正常服務的影響。而在應用程序中,你能夠利用各級緩存、git

WAF、CDN 等方式,緩解 DDoS 對應用程序的影響。github

不過要注意,若是 DDoS 的流量,已經到了 Linux 服務器中,那麼,即便應用層作了各類優化,網絡服務的延遲通常仍是會比正常狀況大不少。算法

因此,在實際應用中,咱們一般要讓 Linux 服務器,配合專業的流量清洗以及網絡防火牆設備,一塊兒來緩解這一問題。docker

除了 DDoS 會帶來網絡延遲增大外,我想,你確定見到過很多其餘緣由致使的網絡延遲,好比緩存

  1. 網絡傳輸慢,致使延遲;
  2. Linux 內核協議棧報文處理慢,致使延遲;
  3. 應用程序數據處理慢,致使延遲等等。

那麼,當碰到這些緣由的延遲時,咱們該怎麼辦呢?又該如何定位網絡延遲的根源呢?今天,我就經過一個案例,帶你一塊兒看看這些問題bash

2、網絡延遲

我相信,提到網絡延遲時,你可能輕鬆想起它的含義——網絡數據傳輸所用的時間。不過要注意,這個時間多是單向的,指從源地址發送到目的地址的單程時間;也多是雙向
的,即從源地址發送到目的地址,而後又從目的地址發回響應,這個往返全程所用的時間。服務器

一般,咱們更經常使用的是雙向的往返通訊延遲,好比 ping 測試的結果,就是往返延時RTT(Round-Trip Time)。

除了網絡延遲外,另外一個經常使用的指標是應用程序延遲,它是指,從應用程序接收到請求,再到發回響應,全程所用的時間。一般,應用程序延遲也指的是往返延遲,是網絡數據傳
輸時間加上數據處理時間的和。

在 Linux 網絡基礎篇 中,我曾經介紹到,你能夠用 ping 來測試網絡延遲。ping 基於ICMP 協議,它經過計算 ICMP 回顯響應報文與 ICMP 回顯請求報文的時間差,來得到往

返延時。這個過程並不須要特殊認證,常被不少網絡攻擊利用,好比端口掃描工具nmap、組包工具 hping3 等等。

因此,爲了不這些問題,不少網絡服務會把 ICMP 禁止掉,這也就致使咱們沒法用 ping,來測試網絡服務的可用性和往返延時。這時,你能夠用 traceroute 或 hping3 的 TCP
和 UDP 模式,來獲取網絡延遲。

好比,以 baidu.com 爲例,你能夠執行下面的 hping3 命令,測試你的機器到百度搜索服務器的網絡延遲:

# -c 表示發送 3 次請求,-S 表示設置 TCP SYN,-p 表示端口號爲 80
$ hping3 -c 3 -S -p 80 baidu.com
HPING baidu.com (eth0 123.125.115.110): S set, 40 headers + 0 data bytes
len=46 ip=123.125.115.110 ttl=51 id=47908 sport=80 flags=SA seq=0 win=8192 rtt=20.9 ms
len=46 ip=123.125.115.110 ttl=51 id=6788  sport=80 flags=SA seq=1 win=8192 rtt=20.9 ms
len=46 ip=123.125.115.110 ttl=51 id=37699 sport=80 flags=SA seq=2 win=8192 rtt=20.9 ms

--- baidu.com hping statistic ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 20.9/20.9/20.9 ms

從 hping3 的結果中,你能夠看到,往返延遲 RTT 爲 20.9ms。

固然,咱們用 traceroute ,也能夠獲得相似結果:

# --tcp 表示使用 TCP 協議,-p 表示端口號,-n 表示不對結果中的 IP 地址執行反向域名解析
$ traceroute --tcp -p 80 -n baidu.com
traceroute to baidu.com (123.125.115.110), 30 hops max, 60 byte packets
 1  * * *
 2  * * *
 3  * * *
 4  * * *
 5  * * *
 6  * * *
 7  * * *
 8  * * *
 9  * * *
10  * * *
11  * * *
12  * * *
13  * * *
14  123.125.115.110  20.684 ms *  20.798 ms

traceroute 會在路由的每一跳發送三個包,並在收到響應後,輸出往返延時。若是無響應或者響應超時(默認 5s),就會輸出一個星號。

知道了基於 TCP 測試網絡服務延遲的方法後,接下來,咱們就經過一個案例,來學習網絡延遲升高時的分析思路。

3、案例準備

下面的案例仍然基於 Ubuntu 18.04,一樣適用於其餘的 Linux 系統。我使用的案例環境是這樣的:

  1. 機器配置:2 CPU,8GB 內存。
  2. 預先安裝 docker、hping三、tcpdump、curl、wrk、Wireshark 等工具,好比 apt-getinstall docker.io hping3 tcpdump curl。

這裏的工具你應該都比較熟悉了,其中 wrk 的安裝和使用方法在 怎麼評估系統的網絡性能中曾經介紹過。若是你尚未安裝,請執行下面的命令來安裝它:

https://github.com/wg/wrk
$ cd wrk
$ apt-get install build-essential -y
$ make
$ sudo cp wrk /usr/local/bin/

因爲 Wireshark 須要圖形界面,若是你的虛擬機沒有圖形界面,就能夠把 Wireshark 安裝到其餘的機器中(好比 Windows 筆記本)。
本次案例用到兩臺虛擬機,我畫了一張圖來表示它們的關係。

接下來,咱們打開兩個終端,分別 SSH 登陸到兩臺機器上(如下步驟,假設終端編號與圖示 VM 編號一致),並安裝上面提到的這些工具。注意, curl 和 wrk 只須要安裝在客戶
端 VM(即 VM2)中。

同之前的案例同樣,下面的全部命令都默認以 root 用戶運行,若是你是用普通用戶身份登錄系統,請運行 sudo su root 命令切換到 root 用戶。

若是安裝過程當中有什麼問題,一樣鼓勵你先本身搜索解決,解決不了的,能夠在留言區向我提問。若是你之前已經安裝過了,就能夠忽略這一點了。

接下來,咱們就進入到案例操做的環節。

4、案例分析

爲了對比得出延遲增大的影響,首先,咱們來運行一個最簡單的 Nginx,也就是用官方的Nginx 鏡像啓動一個容器。在終端一中,執行下面的命令,運行官方 Nginx,它會在 80端口監聽:

docker run --network=host --name=good -itd nginx
fb4ed7cb9177d10e270f8320a7fb64717eac3451114c9fab3c50e02be2e88ba2

繼續在終端一中,執行下面的命令,運行案例應用,它會監聽 8080 端口:

docker run --name nginx --network=host -itd feisky/nginx:latency
b99bd136dcfd907747d9c803fdc0255e578bad6d66f4e9c32b826d75b6812724

而後,在終端二中執行 curl 命令,驗證兩個容器已經正常啓動。若是一切正常,你將看到以下的輸出:

# 80 端口正常
$ curl http://192.168.0.30
<!DOCTYPE html>
<html>
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

# 8080 端口正常
$ curl http://192.168.0.30:8080
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

接着,咱們再用上面提到的 hping3 ,來測試它們的延遲,看看有什麼區別。仍是在終端二,執行下面的命令,分別測試案例機器 80 端口和 8080 端口的延遲:

# 測試 80 端口延遲
$ 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=64 DF id=0 sport=80 flags=SA seq=0 win=29200 rtt=7.8 ms
len=44 ip=192.168.0.30 ttl=64 DF id=0 sport=80 flags=SA seq=1 win=29200 rtt=7.7 ms
len=44 ip=192.168.0.30 ttl=64 DF id=0 sport=80 flags=SA seq=2 win=29200 rtt=7.6 ms

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

8080端口

# 測試 8080 端口延遲
$ hping3 -c 3 -S -p 8080 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=64 DF id=0 sport=8080 flags=SA seq=0 win=29200 rtt=7.7 ms
len=44 ip=192.168.0.30 ttl=64 DF id=0 sport=8080 flags=SA seq=1 win=29200 rtt=7.6 ms
len=44 ip=192.168.0.30 ttl=64 DF id=0 sport=8080 flags=SA seq=2 win=29200 rtt=7.3 ms

--- 192.168.0.30 hping statistic ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 7.3/7.6/7.7 ms

從這個輸出你能夠看到,兩個端口的延遲差很少,都是 7ms。不過,這只是單個請求的狀況。換成併發請求的話,又會怎麼樣呢?接下來,咱們就用 wrk 試試。

此次在終端二中,執行下面的新命令,分別測試案例機器併發 100 時, 80 端口和 8080端口的性能:

# 測試 80 端口性能
$ # wrk --latency -c 100 -t 2 --timeout 2 http://192.168.0.30/
Running 10s test @ http://192.168.0.30/
  2 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     9.19ms   12.32ms 319.61ms   97.80%
    Req/Sec     6.20k   426.80     8.25k    85.50%
  Latency Distribution
     50%    7.78ms
     75%    8.22ms
     90%    9.14ms
     99%   50.53ms
  123558 requests in 10.01s, 100.15MB read
Requests/sec:  12340.91
Transfer/sec:     10.00MB

8080端口性能:

# 測試 8080 端口性能
$ wrk --latency -c 100 -t 2 --timeout 2 http://192.168.0.30:8080/
Running 10s test @ http://192.168.0.30:8080/
  2 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    43.60ms    6.41ms  56.58ms   97.06%
    Req/Sec     1.15k   120.29     1.92k    88.50%
  Latency Distribution
     50%   44.02ms
     75%   44.33ms
     90%   47.62ms
     99%   48.88ms
  22853 requests in 10.01s, 18.55MB read
Requests/sec:   2283.31
Transfer/sec:      1.85MB

從上面兩個輸出能夠看到,官方 Nginx(監聽在 80 端口)的平均延遲是 9.19ms,而案例 Nginx 的平均延遲(監聽在 8080 端口)則是 43.6ms。從延遲的分佈上來看,官方
Nginx 90% 的請求,均可以在 9ms 之內完成;而案例 Nginx 50% 的請求,就已經達到了 44 ms。

再結合上面 hping3 的輸出,咱們很容易發現,案例 Nginx 在併發請求下的延遲增大了不少,這是怎麼回事呢?

分析方法我想你已經想到了,上節課學過的,使用 tcpdump 抓取收發的網絡包,分析網絡的收發過程有沒有問題。

接下來,咱們在終端一中,執行下面的 tcpdump 命令,抓取 8080 端口上收發的網絡包,並保存到 nginx.pcap 文件:

tcpdump -nn tcp port 8080 -w nginx.pcap

而後切換到終端二中,從新執行 wrk 命令:

# 測試 8080 端口性能
$ wrk --latency -c 100 -t 2 --timeout 2 http://192.168.0.30:8080/

當 wrk 命令結束後,再次切換回終端一,並按下 Ctrl+C 結束 tcpdump 命令。而後,再把抓取到的 nginx.pcap ,複製到裝有 Wireshark 的機器中(若是 VM1 已經帶有圖形界
面,那麼能夠跳過複製步驟),並用 Wireshark 打開它。

因爲網絡包的數量比較多,咱們能夠先過濾一下。好比,在選擇一個包後,你能夠單擊右鍵並選擇 「Follow」 -> 「TCP Stream」,以下圖所示

而後,關閉彈出來的對話框,回到 Wireshark 主窗口。這時候,你會發現 Wireshark 已經自動幫你設置了一個過濾表達式 tcp.stream eq 24。以下圖所示(圖中省去了源和目的 IP地址):

實際測試截圖:

從這裏,你能夠看到這個 TCP 鏈接從三次握手開始的每一個請求和響應狀況。固然,這可能還不夠直觀,你能夠繼續點擊菜單欄裏的 Statics -> Flow Graph,選中 「Limit to
display filter」 並設置 Flow type 爲 「TCP Flows」:

實際測試截圖:

注意,這個圖的左邊是客戶端,而右邊是 Nginx 服務器。經過這個圖就能夠看出,前面三次握手,以及第一次 HTTP 請求和響應仍是挺快的,但第二次 HTTP 請求就比較慢了,特
別是客戶端在收到服務器第一個分組後,40ms 後才發出了 ACK 響應(圖中藍色行)。看到 40ms 這個值,你有沒有想起什麼東西呢?實際上,這是 TCP 延遲確認(Delayed
ACK)的最小超時時間。

這裏我解釋一下延遲確認。這是針對 TCP ACK 的一種優化機制,也就是說,不用每次請求都發送一個 ACK,而是先等一下子(好比 40ms),看看有沒有「順風車」。若是這段
時間內,正好有其餘包須要發送,那就捎帶着 ACK 一塊兒發送過去。固然,若是一直等不到其餘包,那就超時後單獨發送 ACK。

由於案例中 40ms 發生在客戶端上,咱們有理由懷疑,是客戶端開啓了延遲確認機制。而這兒的客戶端,實際上就是前面運行的 wrk。

查詢 TCP 文檔(執行 man tcp),你就會發現,只有 TCP 套接字專門設置了TCP_QUICKACK ,纔會開啓快速確認模式;不然,默認狀況下,採用的就是延遲確認機制:

TCP_QUICKACK (since Linux 2.4.4)
              Enable  quickack mode if set or disable quickack mode if cleared.  In quickack mode, acks are sent imme‐
              diately, rather than delayed if needed in accordance to normal TCP operation.  This flag is  not  perma‐
              nent,  it only enables a switch to or from quickack mode.  Subsequent operation of the TCP protocol will
              once again enter/leave quickack mode depending on internal  protocol  processing  and  factors  such  as
              delayed ack timeouts occurring and data transfer.  This option should not be used in code intended to be
              portable.

爲了驗證咱們的猜測,確認 wrk 的行爲,咱們能夠用 strace ,來觀察 wrk 爲套接字設置了哪些 TCP 選項。

好比,你能夠切換到終端二中,執行下面的命令:

strace -f wrk --latency -c 100 -t 2 --timeout 2 http://192.168.0.30:8080/
...
setsockopt(52, SOL_TCP, TCP_NODELAY, [1], 4) = 0
...

這樣,你能夠看到,wrk 只設置了 TCP_NODELAY 選項,而沒有設置 TCP_QUICKACK。這說明 wrk 採用的正是延遲確認,也就解釋了上面這個 40ms 的問題。

不過,別忘了,這只是客戶端的行爲,按理來講,Nginx 服務器不該該受到這個行爲的影響。那是否是咱們分析網絡包時,漏掉了什麼線索呢?讓咱們回到 Wireshark 從新觀察一下。

實際測試截圖:

仔細觀察 Wireshark 的界面,其中, 1173 號包,就是剛纔說到的延遲 ACK 包;下一行的 1175 ,則是 Nginx 發送的第二個分組包,它跟 697 號包組合起來,構成一個完整的
HTTP 響應(ACK 號都是 85)。

第二個分組沒跟前一個分組(697 號)一塊兒發送,而是等到客戶端對第一個分組的 ACK後(1173 號)才發送,這看起來跟延遲確認有點像,只不過,這兒再也不是 ACK,而是發
送數據。

看到這裏,我估計你想起了一個東西—— Nagle 算法(納格算法)。進一步分析案例前,我先簡單介紹一下這個算法。

Nagle 算法,是 TCP 協議中用於減小小包發送數量的一種優化算法,目的是爲了提升實際帶寬的利用率。

舉個例子,當有效負載只有 1 字節時,再加上 TCP 頭部和 IP 頭部分別佔用的 20 字節,整個網絡包就是 41 字節,這樣實際帶寬的利用率只有 2.4%(1/41)。往大了說,若是整
個網絡帶寬都被這種小包占滿,那整個網絡的有效利用率就過低了。

Nagle 算法正是爲了解決這個問題。它經過合併 TCP 小包,提升網絡帶寬的利用率。Nagle 算法規定,一個 TCP 鏈接上,最多隻能有一個未被確認的未完成分組;在收到這個
分組的 ACK 前,不發送其餘分組。這些小分組會被組合起來,並在收到 ACK 後,用同一個分組發送出去。

顯然,Nagle 算法自己的想法仍是挺好的,可是知道 Linux 默認的延遲確認機制後,你應該就不這麼想了。由於它們一塊兒使用時,網絡延遲會明顯。以下圖所示:

當 Sever 發送了第一個分組後,因爲 Client 開啓了延遲確認,就須要等待 40ms 後纔會回覆 ACK。

既然多是 Nagle 的問題,那該怎麼知道,案例 Nginx 有沒有開啓 Nagle 呢?查詢 tcp 的文檔,你就會知道,只有設置了 TCP_NODELAY 後,Nagle 算法纔會禁用。

因此,咱們只須要查看 Nginx 的 tcp_nodelay 選項就能夠了。

TCP_NODELAY
              If set, disable the Nagle algorithm.  This means that segments are always sent as soon as possible, even
              if there is only a small amount of data.  When not set, data is buffered until  there  is  a  sufficient
              amount  to  send out, thereby avoiding the frequent sending of small packets, which results in poor uti‐
              lization of the network.  This option is overridden by TCP_CORK; however, setting this option forces  an
              explicit flush of pending output, even if TCP_CORK is currently set.

咱們回到終端一中,執行下面的命令,查看案例 Nginx 的配置:

docker exec nginx cat /etc/nginx/nginx.conf | grep tcp_nodelay
    tcp_nodelay    off;

果真,你能夠看到,案例 Nginx 的 tcp_nodelay 是關閉的,將其設置爲 on ,應該就能夠解決了。

# 刪除案例應用
$ docker rm -f nginx

# 啓動優化後的應用
$ docker run --name nginx --network=host -itd feisky/nginx:nodelay

改完後,問題是否就解決了呢?天然須要驗證咱們一下。修改後的應用,我已經打包到了Docker 鏡像中,在終端一中執行下面的命令,你就能夠啓動它:

接着,切換到終端二,從新執行 wrk 測試延遲:

wrk --latency -c 100 -t 2 --timeout 2 http://192.168.0.30:8080/
Running 10s test @ http://192.168.0.30:8080/
  2 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     9.58ms   14.98ms 350.08ms   97.91%
    Req/Sec     6.22k   282.13     6.93k    68.50%
  Latency Distribution
     50%    7.78ms
     75%    8.20ms
     90%    9.02ms
     99%   73.14ms
  123990 requests in 10.01s, 100.50MB read
Requests/sec:  12384.04
Transfer/sec:     10.04MB

果真,如今延遲已經縮短成了 9ms,跟咱們測試的官方 Nginx 鏡像是同樣的(Nginx 默認就是開啓 tcp_nodelay 的) 。
做爲對比,咱們用 tcpdump ,抓取優化後的網絡包(這兒實際上抓取的是官方 Nginx 監聽的 80 端口)。你能夠獲得下面的結果:

從圖中你能夠發現,因爲 Nginx 不用再等 ACK,536 和 540 兩個分組是連續發送的;而客戶端呢,雖然仍開啓了延遲確認,但這時收到了兩個須要回覆 ACK 的包,因此也不用等
40ms,能夠直接合並回復 ACK。

案例最後,不要忘記中止這兩個容器應用。在終端一中,執行下面的命令,就能夠刪除案例應用:

5、小結

今天,咱們學習了網絡延遲增大後的分析方法。網絡延遲,是最核心的網絡性能指標。因爲網絡傳輸、網絡包處理等各類因素的影響,網絡延遲不可避免。但過大的網絡延遲,會
直接影響用戶的體驗。

因此,在發現網絡延遲增大後,你能夠用 traceroute、hping三、tcpdump、Wireshark、strace 等多種工具,來定位網絡中的潛在問題。好比,

  1. 使用 hping3 以及 wrk 等工具,確認單次請求和併發請求狀況的網絡延遲是否正常。
  2. 使用 traceroute,確認路由是否正確,並查看路由中每一跳網關的延遲。
  3. 使用 tcpdump 和 Wireshark,確認網絡包的收發是否正常。
  4. 使用 strace 等,觀察應用程序對網絡套接字的調用狀況是否正常。

這樣,你就能夠依次從路由、網絡包的收發、再到應用程序等,逐層排查,直到定位問題根源。

相關文章
相關標籤/搜索