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

1、上節回顧

上一節,咱們一塊兒學習了怎麼使用動態追蹤來觀察應用程序和內核的行爲。先簡單來回顧一下。所謂動態追蹤,就是在系統或者應用程序還在正常運行的時候,經過內核中提供的探針,來動態
追蹤它們的行爲,從而輔助排查出性能問題的瓶頸。php

使用動態追蹤,即可以在不修改代碼也不重啓服務的狀況下,動態瞭解應用程序或者內核的行爲。這對排查線上的問題、特別是不容易重現的問題尤爲有效。linux

在 Linux 系統中,常見的動態追蹤方法包括 ftrace、perf、eBPF/BCC 以及 SystemTap 等。nginx

  1. 使用 perf 配合火焰圖尋找熱點函數,是一個比較通用的性能定位方法,在不少場景中均可以使用。
  2. 若是這仍知足不了你的要求,那麼在新版的內核中,eBPF 和 BCC 是最靈活的動態追蹤方法。
  3. 而在舊版本內核,特別是在 RHEL 系統中,因爲 eBPF 支持受限,SystemTap 和 ftrace 每每是更好的選擇。

在 網絡請求延遲變大 的案例中,我帶你一塊兒分析了一個網絡請求延遲增大的問題。當時咱們分析知道,那是因爲服務器端開啓 TCP 的 Nagle 算法,而客戶端卻開啓了延遲確認所致使的。git

其實,除了延遲問題外,網絡請求的吞吐量降低,是另外一個常見的性能問題。那麼,針對這種吞吐量降低問題,咱們又該如何進行分析呢?github

接下來,我就以最經常使用的反向代理服務器 Nginx 爲例,帶你一塊兒看看,如何分析服務吞吐量降低的問題。算法

2、案例準備

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

  1. 機器配置:2 CPU,8GB 內存。
  2. 預先安裝 docker、curl、wrk、perf、FlameGraph 等工具,好比
# 安裝必備 docker、curl 和 perf
$ apt-get install -y docker.io curl build-essential linux-tools-common
# 安裝火焰圖工具
$ git clone https://github.com/brendangregg/FlameGraph
# 安裝 wrk
$ git clone https://github.com/wg/wrk
$ cd wrk && make && sudo cp wrk /usr/local/bin/

這些工具,咱們在前面的案例中已經屢次使用,這兒就再也不重複。你能夠打開兩個終端,分別登陸到這兩臺虛擬機中,並安裝上述工具。瀏覽器

注意,如下全部命令都默認以 root 用戶運行,若是你用普通用戶身份登錄系統,請運行 sudo su root 命令切換到 root 用戶。性能優化

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

3、案例分析

咱們今天要分析的案例是一個 Nginx + PHP 應用,它們的關係以下圖所示:

其中,wrk 和 curl 是 Nginx 的客戶端,而 PHP 應用則是一個簡單的 Hello World:

<?php
echo "Hello World!"
?>

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

同時,爲了分析方便,這兩個容器都將運行在 host network 模式中。這樣,咱們就不用切換到容器的網絡命名空間,而能夠直接觀察它們的套接字狀態。

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

docker run --name nginx --network host --privileged -itd feisky/nginx-tp
6477c607c13b37943234755a14987ffb3a31c33a7f04f75bb1c190e710bce19e
$ docker run --name phpfpm --network host --privileged -itd feisky/php-fpm-tp
09e0255159f0c8a647e22cd68bd097bec7efc48b21e5d91618ff29b882fa7c1f

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

docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
09e0255159f0        feisky/php-fpm-tp   "php-fpm -F --pid /o…"   28 seconds ago      Up 27 seconds                           phpfpm
6477c607c13b        feisky/nginx-tp     "/init.sh"               29 seconds ago      Up 28 seconds                           nginx

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

接着,切換到終端二中,執行下面的 curl 命令,進一步驗證 Nginx 可否正常訪問。若是你看到「Hello World!」 的輸出,說明 Nginx+PHP 的應用已經正常啓動了:

curl http://192.168.0.30
Hello World!

提示:若是你看到不同的結果,能夠再次執行 docker ps -a 確認容器的狀態,並執行 docker logs < 容器名 > 來查看容器日誌,從而找出緣由

接下來,咱們就來測試一下,案例中 Nginx 的吞吐量。

咱們繼續在終端二中,執行 wrk 命令,來測試 Nginx 的性能:

# 默認測試時間爲 10s,請求超時 2s
$ wrk --latency -c 1000 http://192.168.0.30
Running 10s test @ http://192.168.0.30
  2 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    14.82ms   42.47ms 874.96ms   98.43%
    Req/Sec   550.55      1.36k    5.70k    93.10%
  Latency Distribution
     50%   11.03ms
     75%   15.90ms
     90%   23.65ms
     99%  215.03ms
  1910 requests in 10.10s, 573.56KB read
  Non-2xx or 3xx responses: 1910
Requests/sec:    189.10
Transfer/sec:     56.78KB

從 wrk 的結果中,你能夠看到吞吐量(也就是每秒請求數)只有 189,而且全部 1910 個請求收到的都是異常響應(非 2xx 或 3xx)。這些數據顯然代表,吞吐量過低了,而且請求處理都失敗了。這是怎麼回事呢?

根據 wrk 輸出的統計結果,咱們能夠看到,總共傳輸的數據量只有 573 KB,那就確定不會是帶寬受限致使的。因此,咱們應該從請求數的角度來分析。

分析請求數,特別是 HTTP 的請求數,有什麼好思路嗎?固然就要從 TCP 鏈接數入手。

4、鏈接數優化

要查看 TCP 鏈接數的彙總狀況,首選工具天然是 ss 命令。爲了觀察 wrk 測試時發生的問題,咱們在終端二中再次啓動 wrk,而且把總的測試時間延長到 30 分鐘:

# 測試時間 30 分鐘
$ wrk --latency -c 1000 -d 1800 http://192.168.0.30

而後,回到終端一中,觀察 TCP 鏈接數:

ss -s
Total: 177 (kernel 1565)
TCP:   1193 (estab 5, closed 1178, orphaned 0, synrecv 0, timewait 1178/0), ports 0

Transport Total     IP        IPv6
*	  1565      -         -
RAW	  1         0         1
UDP	  2         2         0
TCP	  15        12        3
INET	  18        14        4
FRAG	  0         0         0

從這裏看出,wrk 併發 1000 請求時,創建鏈接數只有 5,而 closed 和 timewait 狀態的鏈接則有 1100 多 。其實從這兒你就能夠發現兩個問題:

  1. 一個是創建鏈接數太少了;
  2. 另外一個是 timewait 狀態鏈接太多了。

分析問題,天然要先從相對簡單的下手。咱們先來看第二個關於 timewait 的問題。在以前的NAT 案例中,我已經提到過,內核中的鏈接跟蹤模塊,有可能會致使 timewait 問題。咱們今天
的案例仍是基於 Docker 運行,而 Docker 使用的 iptables ,就會使用鏈接跟蹤模塊來管理NAT。那麼,怎麼確認是否是鏈接跟蹤致使的問題呢?

其實,最簡單的方法,就是經過 dmesg 查看系統日誌,若是有鏈接跟蹤出了問題,應該會看到nf_conntrack 相關的日誌。

咱們能夠繼續在終端一中,運行下面的命令,查看系統日誌:

dmesg | tail
[88356.354329] nf_conntrack: nf_conntrack: table full, dropping packet
[88356.354374] nf_conntrack: nf_conntrack: table full, dropping packet

從日誌中,你能夠看到 nf_conntrack: table full, dropping packet 的錯誤日誌。這說明,正是鏈接跟蹤致使的問題。

這種狀況下,咱們應該想起前面學過的兩個內核選項——鏈接跟蹤數的最大限制nf_conntrack_max ,以及當前的鏈接跟蹤數 nf_conntrack_count。執行下面的命令,你就能夠
查詢這兩個選項:

sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 200
$ sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 200

此次的輸出中,你能夠看到最大的鏈接跟蹤限制只有 200,而且所有被佔用了。200 的限制顯然過小,不過相應的優化也很簡單,調大就能夠了。

咱們執行下面的命令,將 nf_conntrack_max 增大:

# 將鏈接跟蹤限制增大到 1048576
$ sysctl -w net.netfilter.nf_conntrack_max=1048576

鏈接跟蹤限制增大後,對 Nginx 吞吐量的優化效果如何呢?咱們不妨再來測試一下。你能夠切換到終端二中,按下 Ctrl+C ;而後執行下面的 wrk 命令,從新測試 Nginx 的性能:

# 默認測試時間爲 10s,請求超時 2s
$ wrk --latency -c 1000 http://192.168.0.30
...
  54221 requests in 10.07s, 15.16MB read
  Socket errors: connect 0, read 7, write 0, timeout 110
  Non-2xx or 3xx responses: 45577
Requests/sec:   5382.21
Transfer/sec:      1.50MB

從 wrk 的輸出中,你能夠看到,鏈接跟蹤的優化效果很是好,吞吐量已經從剛纔的 189 增大到了 5382。看起來性能提高了將近 30 倍,

不過,這是否是就能說明,咱們已經把 Nginx 的性能優化好了呢?

別急,咱們再來看看 wrk 彙報的其餘數據。果真,10s 內的總請求數雖然增大到了 5 萬,可是有4 萬多響應異常,說白了,真正成功的只有 8000 多個(54221-45577=8644)。

很明顯,大部分請求的響應都是異常的。那麼,該怎麼分析響應異常的問題呢?

5、工做進程優化

因爲這些響應並不是 Socket error,說明 Nginx 已經收到了請求,只不過,響應的狀態碼並非咱們指望的 2xx (表示成功)或 3xx(表示重定向)。因此,這種狀況下,搞清楚 Nginx 真正
的響應就很重要了。

不過這也不難,咱們切換回終端一,執行下面的 docker 命令,查詢 Nginx 容器日誌就知道了:

docker logs nginx --tail 3
192.168.0.2 - - [15/Mar/2019:2243:27 +0000] "GET / HTTP/1.1" 499 0 "-" "-" "-"
192.168.0.2 - - [15/Mar/2019:22:43:27 +0000] "GET / HTTP/1.1" 499 0 "-" "-" "-"
192.168.0.2 - - [15/Mar/2019:22:43:27 +0000] "GET / HTTP/1.1" 499 0 "-" "-" "-"

從 Nginx 的日誌中,咱們能夠看到,響應狀態碼爲 499。

499 並不是標準的 HTTP 狀態碼,而是由 Nginx 擴展而來,表示服務器端還沒來得及響應時,客戶端就已經關閉鏈接了。換句話說,問題在於服務器端處理太慢,客戶端由於超時(wrk 超時時
間爲 2s),主動斷開了鏈接。

既然問題出在了服務器端處理慢,而案例自己是 Nginx+PHP 的應用,那是否是能夠猜想,是由於 PHP 處理過慢呢?

我麼能夠在終端中,執行下面的 docker 命令,查詢 PHP 容器日誌:

docker logs phpfpm --tail 5
[15-Mar-2019 22:28:56] WARNING: [pool www] server reached max_children setting (5), consider raising it
[15-Mar-2019 22:43:17] WARNING: [pool www] server reached max_children setting (5), consider raising it

從這個日誌中,咱們能夠看到兩條警告信息,server reached max_children setting (5),並建議增大 max_children。

max_children 表示 php-fpm 子進程的最大數量,固然是數值越大,能夠同時處理的請求數就越多。不過因爲這是進程問題,數量增大,也會致使更多的內存和 CPU 佔用。因此,咱們還不能
設置得過大。

通常來講,每一個 php-fpm 子進程可能會佔用 20 MB 左右的內存。因此,你能夠根據內存和CPU 個數,估算一個合理的值。這兒我把它設置成了 20,並將優化後的配置從新打包成了
Docker 鏡像。你能夠執行下面的命令來執行它:

# 中止舊的容器
$ docker rm -f nginx phpfpm

# 使用新鏡像啓動 Nginx 和 PHP
$ docker run --name nginx --network host --privileged -itd feisky/nginx-tp:1
$ docker run --name phpfpm --network host --privileged -itd feisky/php-fpm-tp:1

而後咱們切換到終端二,再次執行下面的 wrk 命令,從新測試 Nginx 的性能:

# 默認測試時間爲 10s,請求超時 2s
$ wrk --latency -c 1000 http://192.168.0.30
...
  47210 requests in 10.08s, 12.51MB read
  Socket errors: connect 0, read 4, write 0, timeout 91
  Non-2xx or 3xx responses: 31692
Requests/sec:   4683.82
Transfer/sec:      1.24MB

從 wrk 的輸出中,能夠看到,雖然吞吐量只有 4683,比剛纔的 5382 少了一些;可是測試期間成功的請求數卻多了很多,從原來的 8000,增加到了 15000(47210-31692=15518)。

不過,雖然性能有所提高,可 4000 多的吞吐量顯然仍是比較差的,而且大部分請求的響應依然仍是異常。接下來,該怎麼去進一步提高 Nginx 的吞吐量呢?

6、套接字優化

回想一下網絡性能的分析套路,以及 Linux 協議棧的原理,咱們能夠從從套接字、TCP 協議等逐層分析。而分析的第一步,天然仍是要觀察有沒有發生丟包現象。

咱們切換到終端二中,從新運行測試,此次仍是要用 -d 參數延長測試時間,以便模擬性能瓶頸的現場:

# 測試時間 30 分鐘
$ wrk --latency -c 1000 -d 1800 http://192.168.0.30

而後回到終端一中,觀察有沒有發生套接字的丟包現象:

# 只關注套接字統計
$ netstat -s | grep socket
    73 resets received for embryonic SYN_RECV sockets
    308582 TCP sockets finished time wait in fast timer
    8 delayed acks further delayed because of locked socket
    290566 times the listen queue of a socket overflowed
    290566 SYNs to LISTEN sockets dropped

# 稍等一會,再次運行
$ netstat -s | grep socket
    73 resets received for embryonic SYN_RECV sockets
    314722 TCP sockets finished time wait in fast timer
    8 delayed acks further delayed because of locked socket
    344440 times the listen queue of a socket overflowed
    344440 SYNs to LISTEN sockets dropped

根據兩次統計結果中 socket overflowed 和 sockets dropped 的變化,你能夠看到,有大量的套接字丟包,而且丟包都是套接字隊列溢出致使的。因此,接下來,咱們應該分析鏈接隊列的大
小是否是有異常。

你能夠執行下面的命令,查看套接字的隊列大小:

ss -ltnp
State     Recv-Q     Send-Q            Local Address:Port            Peer Address:Port
LISTEN    10         10                      0.0.0.0:80                   0.0.0.0:*         users:(("nginx",pid=10491,fd=6),("nginx",pid=10490,fd=6),("nginx",pid=10487,fd=6))
LISTEN    7          10                            *:9000                       *:*         users:(("php-fpm",pid=11084,fd=9),...,("php-fpm",pid=10529,fd=7))

此次能夠看到,Nginx 和 php-fpm 的監聽隊列 (Send-Q)只有 10,而 nginx 的當前監聽隊列長度 (Recv-Q)已經達到了最大值,php-fpm 也已經接近了最大值。很明顯,套接字監聽隊
列的長度過小了,須要增大。

關於套接字監聽隊列長度的設置,既能夠在應用程序中,經過套接字接口調整,也支持經過內核選項來配置。咱們繼續在終端一中,執行下面的命令,分別查詢 Nginx 和內核選項對監聽隊列長
度的配置:

# 查詢 nginx 監聽隊列長度配置
$ docker exec nginx cat /etc/nginx/nginx.conf | grep backlog
        listen       80  backlog=10;

# 查詢 php-fpm 監聽隊列長度
$ docker exec phpfpm cat /opt/bitnami/php/etc/php-fpm.d/www.conf | grep backlog
; Set listen(2) backlog.
;listen.backlog = 511

# somaxconn 是系統級套接字監聽隊列上限
$ sysctl net.core.somaxconn
net.core.somaxconn = 10

從輸出中能夠看到,Nginx 和 somaxconn 的配置都是 10,而 php-fpm 的配置也只有 511,顯然都過小了。那麼,優化方法就是增大這三個配置,好比,能夠把 Nginx 和 php-fpm 的隊列
長度增大到 8192,而把 somaxconn 增大到 65536。

一樣地,我也把這些優化後的 Nginx ,從新打包成了兩個 Docker 鏡像,你能夠執行下面的命令來運行它:

# 中止舊的容器
$ docker rm -f nginx phpfpm

# 使用新鏡像啓動 Nginx 和 PHP
$ docker run --name nginx --network host --privileged -itd feisky/nginx-tp:2
$ docker run --name phpfpm --network host --privileged -itd feisky/php-fpm-tp:2

而後,切換到終端二中,從新測試 Nginx 的性能:

wrk --latency -c 1000 http://192.168.0.30
...
  62247 requests in 10.06s, 18.25MB read
  Non-2xx or 3xx responses: 62247
Requests/sec:   6185.65
Transfer/sec:      1.81MB

如今的吞吐量已經增大到了 6185,而且在測試的時候,若是你在終端一中從新執行 netstat -s |grep socket,還會發現,如今已經沒有套接字丟包問題了。

不過,此次 Nginx 的響應,再一次所有失敗了,都是 Non-2xx or 3xx。這是怎麼回事呢?咱們再去終端一中,查看 Nginx 日誌:

docker logs nginx --tail 10
2019/03/15 16:52:39 [crit] 15#15: *999779 connect() to 127.0.0.1:9000 failed (99: Cannot assign requested address) while connecting to upstream, client: 192.168.0.2, server: localhost, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "192.168.0.30"

你能夠看到,Nginx 報出了沒法鏈接 fastcgi 的錯誤,錯誤消息是 Connect 時, Cannot assignrequested address。這個錯誤消息對應的錯誤代碼爲 EADDRNOTAVAIL,表示 IP 地址或者端
口號不可用。在這裏,顯然只能是端口號的問題。接下來,咱們就來分析端口號的狀況。

7、端口號優化

根據網絡套接字的原理,當客戶端鏈接服務器端時,須要分配一個臨時端口號,而 Nginx 正是PHP-FPM 的客戶端。端口號的範圍並非無限的,最多也只有 6 萬多。

咱們執行下面的命令,就能夠查詢系統配置的臨時端口號範圍:

sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range=20000 20050

根據網絡套接字的原理,當客戶端鏈接服務器端時,須要分配一個臨時端口號,而 Nginx 正是PHP-FPM 的客戶端。端口號的範圍並非無限的,最多也只有 6 萬多。

咱們執行下面的命令,就能夠查詢系統配置的臨時端口號範圍:

sysctl -w net.ipv4.ip_local_port_range="10000 65535"
net.ipv4.ip_local_port_range = 10000 65535

優化完成後,咱們再次切換到終端二中,測試性能:

wrk --latency -c 1000 http://192.168.0.30/
...
  32308 requests in 10.07s, 6.71MB read
  Socket errors: connect 0, read 2027, write 0, timeout 433
  Non-2xx or 3xx responses: 30
Requests/sec:   3208.58
Transfer/sec:    682.15KB

此次,異常的響應少多了 ,不過,吞吐量也降低到了 3208。而且,此次還出現了不少 Socketread errors。顯然,還得進一步優化。

7、火焰圖

前面咱們已經優化了不少配置。這些配置在優化網絡的同時,卻也會帶來其餘資源使用的上升。
這樣來看,是否是說明其餘資源遇到瓶頸了呢?

咱們不妨在終端二中,執行下面的命令,從新啓動長時間測試:

# 測試時間 30 分鐘
$ wrk --latency -c 1000 -d 1800 http://192.168.0.30

而後,切換回終端一中,執行 top ,觀察 CPU 和內存的使用:

top
...
%Cpu0  : 30.7 us, 48.7 sy,  0.0 ni,  2.3 id,  0.0 wa,  0.0 hi, 18.3 si,  0.0 st
%Cpu1  : 28.2 us, 46.5 sy,  0.0 ni,  2.0 id,  0.0 wa,  0.0 hi, 23.3 si,  0.0 st
KiB Mem :  8167020 total,  5867788 free,   490400 used,  1808832 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  7361172 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
20379 systemd+  20   0   38068   8692   2392 R  36.1  0.1   0:28.86 nginx
20381 systemd+  20   0   38024   8700   2392 S  33.8  0.1   0:29.29 nginx
 1558 root      20   0 1118172  85868  39044 S  32.8  1.1  22:55.79 dockerd
20313 root      20   0   11024   5968   3956 S  27.2  0.1   0:22.78 docker-containe
13730 root      20   0       0      0      0 I   4.0  0.0   0:10.07 kworker/u4:0-ev

從 top 的結果中能夠看到,可用內存仍是很充足的,但系統 CPU 使用率(sy)比較高,兩個CPU 的系統 CPU 使用率都接近 50%,且空閒 CPU 使用率只有 2%。再看進程部分,CPU 主要
被兩個 Nginx 進程和兩個 docker 相關的進程佔用,使用率都是 30% 左右。

CPU 使用率上升了,該怎麼進行分析呢?我想,你已經還記得咱們屢次用到的 perf,再配合前兩節講過的火焰圖,很容易就能找到系統中的熱點函數。

咱們保持終端二中的 wrk 繼續運行;在終端一中,執行 perf 和 flamegraph 腳本,生成火焰圖:

# 執行 perf 記錄事件
$ perf record -g

# 切換到 FlameGraph 安裝路徑執行下面的命令生成火焰圖
$ perf script -i ~/perf.data | ./stackcollapse-perf.pl --all | ./flamegraph.pl > nginx.svg

而後,使用瀏覽器打開生成的 nginx.svg ,你就能夠看到下面的火焰圖:

根據咱們講過的火焰圖原理,這個圖應該從下往上、沿着調用棧中最寬的函數,來分析執行次數最多的函數。

這兒中間的 do_syscall_6四、tcp_v4_connect、inet_hash_connect 這個堆棧,很明顯就是最須要關注的地方。inet_hash_connect() 是 Linux 內核中負責分配臨時端口號的函數。因此,這個
瓶頸應該還在臨時端口的分配上。

在上一步的「端口號」優化中,臨時端口號的範圍,已經優化成了 「10000 65535」。這顯然是一個很是大的範圍,那麼,端口號的分配爲何又成了瓶頸呢?

一時想不到也不要緊,咱們能夠暫且放下,先看看其餘因素的影響。再順着 inet_hash_connect往堆棧上面查看,下一個熱點是 __init_check_established 函數。而這個函數的目的,是檢查端
口號是否可用。結合這一點,你應該能夠想到,若是有大量鏈接佔用着端口,那麼檢查端口號可用的函數,不就會消耗更多的 CPU 嗎?

實際是否如此呢?咱們能夠繼續在終端一中運行 ss 命令, 查看鏈接狀態統計:

ss -s
TCP:   32775 (estab 1, closed 32768, orphaned 0, synrecv 0, timewait 32768/0), ports 0
...

這回能夠看到,有大量鏈接(這兒是 32768)處於 timewait 狀態,而 timewait 狀態的鏈接,自己會繼續佔用端口號。若是這些端口號能夠重用,那麼天然就能夠縮短

__init_check_established 的過程。而 Linux 內核中,剛好有一個 tcp_tw_reuse 選項,用來控制端口號的重用。

咱們在終端一中,運行下面的命令,查詢它的配置:

sysctl net.ipv4.tcp_tw_reuse
net.ipv4.tcp_tw_reuse = 0

你能夠看到,tcp_tw_reuse 是 0,也就是禁止狀態。其實看到這裏,咱們就能理解,爲何臨時端口號的分配會是系統運行的熱點了。固然,優化方法也很容易,把它設置成 1 就能夠開啓了。
我把優化後的應用,也打包成了兩個 Docker 鏡像,你能夠執行下面的命令來運行:

# 中止舊的容器
$ docker rm -f nginx phpfpm

# 使用新鏡像啓動 Nginx 和 PHP
$ docker run --name nginx --network host --privileged -itd feisky/nginx-tp:3
$ docker run --name phpfpm --network host --privileged -itd feisky/php-fpm-tp:3

容器啓動後,切換到終端二中,再次測試優化後的效果:

wrk --latency -c 1000 http://192.168.0.30/
...
  52119 requests in 10.06s, 10.81MB read
  Socket errors: connect 0, read 850, write 0, timeout 0
Requests/sec:   5180.48
Transfer/sec:      1.07MB

如今的吞吐量已經達到了 5000 多,而且只有少許的 Socket errors,也再也不有 Non-2xx or 3xx的響應了。說明一切終於正常了。

案例的最後,不要忘記執行下面的命令,刪除案例應用:

# 中止 nginx 和 phpfpm 容器
$ docker rm -f nginx phpfpm

8、小結

今天,我帶你一塊兒學習了服務吞吐量降低後的分析方法。其實,從這個案例你也能夠看出,性能問題的分析,老是離不開系統和應用程序的原理。

實際上,分析性能瓶頸,最核心的也正是掌握運用這些原理。

首先,利用各類性能工具,收集想要的性能指標,從而清楚系統和應用程序的運行狀態;

其次,拿目前狀態跟系統原理進行比較,不一致的地方,就是咱們要重點分析的對象。

從這個角度出發,再進一步藉助 perf、火焰圖、bcc 等動態追蹤工具,找出熱點函數,就能夠定位瓶頸的來源,肯定相應的優化方法。

相關文章
相關標籤/搜索