1. 程式人生 > >10.系統的軟中斷CPU使用率升高

10.系統的軟中斷CPU使用率升高

上一篇說了軟中斷的基本原理,先回看下。

中斷是一種非同步的事件處理機制,用來提高系統的併發處理能力。中斷事件發生,會觸發執行中 斷處理程式,而中斷處理程式被分為上半部和下半部這兩個部分。

  • 上半部對應硬中斷,用來快速處理中斷;
  • 下半部對應軟中斷,用來非同步處理上半部未完成的工作。

Linux 中的軟中斷包括網路收發、定時、排程、RCU 鎖等各種型別,我們可以檢視 proc 檔案系 統中的 /proc/softirqs ,觀察軟中斷的執行情況。

在 Linux 中,每個 CPU 都對應一個軟中斷核心執行緒,名字是 ksoftirqd/CPU 編號。當軟中斷 事件的頻率過高時,核心執行緒也會因為 CPU 使用率過高而導致軟中斷處理不及時,進而引發網路收發延遲、排程緩慢等效能問題。

軟中斷 CPU 使用率過高也是一種最常見的效能問題。今天,我就用最常見的反向代理伺服器 Nginx 的案例,教你學會分析這種情況。

案例

準備

接下來的案例基於 Ubuntu 18.04,也同樣適用於其他的 Linux 系統。我使用的案例環境是這樣 的:

  • 機器配置:2 CPU、8 GB 記憶體。
  • 預先安裝 docker、sysstat、sar 、hping3、tcpdump 等工具,比如 apt-get install docker.io sysstat hping3 tcpdump。

這裡我又用到了三個新工具,sar、 hping3 和 tcpdump,先簡單介紹一下:

  • sar 是一個系統活動報告工具,既可以實時檢視系統的當前活動,又可以配置儲存和報告歷史 統計資料。
  • hping3 是一個可以構造 TCP/IP 協議資料包的工具,可以對系統進行安全審計、防火牆測試 等。
  • tcpdump 是一個常用的網路抓包工具,常用來分析各種網路問題。

本次案例用到兩臺虛擬機器,我畫了一張圖來表示它們的關係。

你可以看到,其中一臺虛擬機器執行 Nginx ,用來模擬待分析的 Web 伺服器;而另一臺當作 Web 伺服器的客戶端,用來給 Nginx 增加壓力請求。使用兩臺虛擬機器的目的,是為了相互隔 離,避免“交叉感染”。

接下來,我們開啟兩個終端,分別 SSH 登入到兩臺機器上,並安裝上面提到的這些工具。

同以前的案例一樣,下面的所有命令都預設以 root 使用者執行,如果你是用普通使用者身份登陸系 統,請執行 sudo su root 命令切換到 root 使用者。

如果安裝過程中有什麼問題,同樣鼓勵你先自己搜尋解決,解決不了的,可以在留言區向我提 問。如果你以前已經安裝過了,就可以忽略這一點了。

操作和分析

安裝完成後,我們先在第一個終端,執行下面的命令執行案例,也就是一個最基本的 Nginx 應 用:

 

# 執行 Nginx 服務並對外開放 80 埠
$ docker run -itd --name=nginx -p 80:80 nginx

然後,在第二個終端,使用 curl 訪問 Nginx 監聽的埠,確認 Nginx 正常啟動。假設 192.168.0.30 是 Nginx 所在虛擬機器的 IP 地址,執行 curl 命令後你應該會看到下面這個輸出界 面:

$ curl http://192.168.0.30/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

接著,還是在第二個終端,我們執行 hping3 命令,來模擬 Nginx 的客戶端請求:

# -S 引數表示設定 TCP 協議的 SYN(同步序列號),-p 表示目的埠為 80
# -i u100 表示每隔 100 微秒傳送一個網路幀
# 注:如果你在實踐過程中現象不明顯,可以嘗試把 100 調小,比如調成 10 甚至 1
$ hping3 -S -p 80 -i u100 192.168.0.30

現在我們再回到第一個終端,你應該發現了異常。是不是感覺系統響應明顯變慢了,即便只是在 終端中敲幾個回車,都得很久才能得到響應?這個時候應該怎麼辦呢?

雖然在執行 hping3 命令時,我就已經告訴你,這是一個 SYN FLOOD 攻擊,你肯定也會想到 從網路方面入手,來分析這個問題。不過,在實際的生產環境中,沒人直接告訴你原因。

所以,我希望你把 hping3 模擬 SYN FLOOD 這個操作暫時忘掉,然後重新從觀察到的問題開 始,分析系統的資源使用情況,逐步找出問題的根源。

那麼,該從什麼地方入手呢?剛才我們發現,簡單的 SHELL 命令都明顯變慢了,先看看系統的 整體資源使用情況應該是個不錯的注意,比如執行下 top 看看是不是出現了 CPU 的瓶頸。我們 在第一個終端執行 top 命令,看一下系統整體的資源使用情況。

# top 執行後按數字 1 切換到顯示所有 CPU
$ top
top - 10:50:58 up 1 days, 22:10, 1 user, load average: 0.00, 0.00, 0.00
Tasks: 122 total, 1 running, 71 sleeping, 0 stopped, 0 zombie
%Cpu0 : 0.0 us, 0.0 sy, 0.0 ni, 96.7 id, 0.0 wa, 0.0 hi, 3.3 si, 0.0 st
%Cpu1 : 0.0 us, 0.0 sy, 0.0 ni, 95.6 id, 0.0 wa, 0.0 hi, 4.4 si, 0.0 st
...
  PID USER PR  NI    VIRT   RES   SHR S   %CPU   %MEM    TIME+ COMMAND
    7 root 20   0       0     0     0 S    0.3    0.0  0:01.64 ksoftirqd/0
   16 root 20   0       0     0     0 S    0.3    0.0  0:01.97 ksoftirqd/1
 2663 root 20   0  923480 28292 13996 S    0.3    0.3  4:58.66 docker-containe
 3699 root 20   0       0     0     0 I    0.3    0.0  0:00.13 kworker/u4:0
 3708 root 20   0   44572  4176  3512 R    0.3    0.1  0:00.07 top
    1 root 20   0  225384  9136  6724 S    0.0    0.1  0:23.25 systemd
    2 root 20   0       0     0     0 S    0.0    0.0  0:00.03 kthreadd
  ...

這裡你有沒有發現異常的現象?我們從第一行開始,逐個看一下:

  • 平均負載全是 0,就緒佇列裡面只有一個程序(1 running)。
  • 每個 CPU 的使用率都挺低,最高的 CPU1 的使用率也只有 4.4%,並不算高。
  • 再看程序列表,CPU 使用率最高的程序也只有 0.3%,還是不高呀。

那為什麼系統的響應變慢了呢?既然每個指標的數值都不大,那我們就再來看看,這些指標對應 的更具體的含義。畢竟,哪怕是同一個指標,用在系統的不同部位和場景上,都有可能對應著不 同的效能問題。

仔細看 top 的輸出,兩個 CPU 的使用率雖然分別只有 3.3% 和 4.4%,但都用在了軟中斷上; 而從程序列表上也可以看到,CPU 使用率最高的也是軟中斷程序 ksoftirqd。看起來,軟中斷有 點可疑了。

根據上一期的內容,既然軟中斷可能有問題,那你先要知道,究竟是哪類軟中斷的問題。停下來 想想,上一節我們用了什麼方法,來判斷軟中斷型別呢?沒錯,還是 proc 檔案系統。觀察 /proc/softirqs 檔案的內容,你就能知道各種軟中斷型別的次數。

不過,這裡的各類軟中斷次數,又是什麼時間段裡的次數呢?它是系統執行以來的累積中斷次 數。所以我們直接檢視檔案內容,得到的只是累積中斷次數,對這裡的問題並沒有直接參考意 義。因為,這些中斷次數的變化速率才是我們需要關注的。

那什麼工具可以觀察命令輸出的變化情況呢?我想你應該想起來了,在前面案例中用過的 watch 命令,就可以定期執行一個命令來檢視輸出;如果再加上 -d 引數,還可以高亮出變化的 部分,從高亮部分我們就可以直觀看出,哪些內容變化得更快。

比如,還是在第一個終端,我們執行下面的命令:

$ watch -d cat /proc/softirqs
                    CPU0       CPU1
         HI:           0          0
      TIMER:     1083906    2368646
     NET_TX:          53          9
     NET_RX:     1550643    1916776
      BLOCK:           0          0
   IRQ_POLL:           0          0
    TASKLET:      333637       3930
      SCHED:      963675    2293171
    HRTIMER:           0          0
        RCU:     1542111    1590625

通過 /proc/softirqs 檔案內容的變化情況,你可以發現, TIMER(定時中斷)、NET_RX(網 絡接收)、SCHED(核心排程)、RCU(RCU 鎖)等這幾個軟中斷都在不停變化。

其中,NET_RX,也就是網路資料包接收軟中斷的變化速率最快。而其他幾種型別的軟中斷,是 保證 Linux 排程、時鐘和臨界區保護這些正常工作所必需的,所以它們有一定的變化倒是正常 的。

那麼接下來,我們就從網路接收的軟中斷著手,繼續分析。既然是網路接收的軟中斷,第一步應 該就是觀察系統的網路接收情況。這裡你可能想起了很多網路工具,不過,我推薦今天的主人公 工具 sar 。

sar 可以用來檢視系統的網路收發情況,還有一個好處是,不僅可以觀察網路收發的吞吐量 (BPS,每秒收發的位元組數),還可以觀察網路收發的 PPS,即每秒收發的網路幀數。

我們在第一個終端中執行 sar 命令,並新增 -n DEV 引數顯示網路收發的報告:

# -n DEV 表示顯示網路收發的報告,間隔 1 秒輸出一組資料
$ sar -n DEV 1
15:03:46      IFACE     rxpck/s    txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s   rxmcst/s %ifutil
15:03:47       eth0    12607.00    6304.00    664.86    358.11      0.00      0.00       0.00    0.01
15:03:47    docker0     6302.00   12604.00    270.79    664.66      0.00      0.00       0.00    0.00
15:03:47         lo        0.00       0.00      0.00      0.00      0.00      0.00       0.00    0.00
15:03:47  veth9f6bbcd   6302.00   12604.00    356.95    664.66      0.00      0.00       0.00    0.05

對於 sar 的輸出介面,我先來簡單介紹一下,從左往右依次是:

  • 第一列:表示報告的時間。
  • 第二列:IFACE 表示網絡卡。
  • 第三、四列:rxpck/s 和 txpck/s 分別表示每秒接收、傳送的網路幀數,也就是 PPS。
  • 第五、六列:rxkB/s 和 txkB/s 分別表示每秒接收、傳送的千位元組數,也就是 BPS。
  • 後面的其他引數基本接近 0,顯然跟今天的問題沒有直接關係,你可以先忽略掉。

我們具體來看輸出的內容,你可以發現:

  • 對網絡卡 eth0 來說,每秒接收的網路幀數比較大,達到了 12607,而傳送的網路幀數則比較 小,只有 6304;每秒接收的千位元組數只有 664 KB,而傳送的千位元組數更小,只有 358 KB。
  • docker0 和 veth9f6bbcd 的資料跟 eth0 基本一致,只是傳送和接收相反,傳送的資料較大 而接收的資料較小。這是 Linux 內部網橋轉發導致的,你暫且不用深究,只要知道這是系統 把 eth0 收到的包轉發給 Nginx 服務即可。具體工作原理,我會在後面的網路部分詳細介 紹。

從這些資料,你有沒有發現什麼異常的地方?

既然懷疑是網路接收中斷的問題,我們還是重點來看 eth0 :接收的 PPS 比較大,達到 12607,而接收的 BPS 卻很小,只有 664 KB。直觀來看網路幀應該都是比較小的,我們稍微計 算一下,664*1024/12607 = 54 位元組,說明平均每個網路幀只有 54 位元組,這顯然是很小的網 絡幀,也就是我們通常所說的小包問題。

那麼,有沒有辦法知道這是一個什麼樣的網路幀,以及從哪裡發過來的呢?

使用 tcpdump 抓取 eth0 上的包就可以了。我們事先已經知道, Nginx 監聽在 80 埠,它所 提供的 HTTP 服務是基於 TCP 協議的,所以我們可以指定 TCP 協議和 80 埠精確抓包。

接下來,我們在第一個終端中執行 tcpdump 命令,通過 -i eth0 選項指定網絡卡 eth0,並通過 tcp port 80 選項指定 TCP 協議的 80 埠:

# -i eth0 只抓取 eth0 網絡卡,-n 不解析協議名和主機名
# tcp port 80 表示只抓取 tcp 協議並且埠號為 80 的網路幀
$ tcpdump -i eth0 -n tcp port 80
15:11:32.678966 IP 192.168.0.2.18238 > 192.168.0.30.80: Flags [S], seq 458303614, win 512, length 0
...

從 tcpdump 的輸出中,你可以發現

  • 192.168.0.2.18238 > 192.168.0.30.80 ,表示網路幀從 192.168.0.2 的 18238 埠傳送到 192.168.0.30 的 80 埠,也就是從執行 hping3 機器的 18238 埠傳送網路幀,目的為 Nginx 所在機器的 80 埠。
  • Flags [S] 則表示這是一個 SYN 包。

再加上前面用 sar 發現的, PPS 超過 12000 的現象,現在我們可以確認,這就是從 192.168.0.2 這個地址傳送過來的 SYN FLOOD 攻擊。

到這裡,我們已經做了全套的效能診斷和分析。從系統的軟中斷使用率高這個現象出發,通過觀 察 /proc/softirqs 檔案的變化情況,判斷出軟中斷型別是網路接收中斷;再通過 sar 和 tcpdump ,確認這是一個 SYN FLOOD 問題。

SYN FLOOD 問題最簡單的解決方法,就是從交換機或者硬體防火牆中封掉來源 IP,這樣 SYN FLOOD 網路幀就不會發送到伺服器中。

至於 SYN FLOOD 的原理和更多解決思路,你暫時不需要過多關注,後面的網路章節裡我們都 會學到。

案例結束後,也不要忘了收尾,記得停止最開始啟動的 Nginx 服務以及 hping3 命令。

在第一個終端中,執行下面的命令就可以停止 Nginx 了:

# 停止 Nginx 服務
$ docker rm -f nginx

然後到第二個終端中按下 Ctrl+C 就可以停止 hping3。

小結

軟中斷 CPU 使用率(softirq)升高是一種很常見的效能問題。雖然軟中斷的型別很多,但實際 生產中,我們遇到的效能瓶頸大多是網路收發型別的軟中斷,特別是網路接收的軟中斷。 在碰到這類問題時,你可以借用 sar、tcpdump 等工具,做進一步分析。不要害怕網路效能, 後面會有更多的分析方法。