系統(tǒng)的軟中斷CPU使用率升高,我該怎么辦?
點擊上方“程序員大白”,選擇“星標”公眾號
重磅干貨,第一時間送達

中斷是一種異步的事件處理機制,用來提高系統(tǒng)的并發(fā)處理能力。中斷事件發(fā)生,會觸發(fā)執(zhí)行中斷處理程序,而中斷處理程序被分為上半部和下半部這兩個部分。
上半部對應硬中斷,用來快速處理中斷; 下半部對應軟中斷,用來異步處理上半部未完成的工作。
Linux 中的軟中斷包括網(wǎng)絡收發(fā)、定時、調(diào)度、RCU 鎖等各種類型,我們可以查看 proc 文件系統(tǒng)中的 /proc/softirqs ,觀察軟中斷的運行情況。
在 Linux 中,每個 CPU 都對應一個軟中斷內(nèi)核線程,名字是 ksoftirqd/CPU 編號。當軟中斷事件的頻率過高時,內(nèi)核線程也會因為 CPU 使用率過高而導致軟中斷處理不及時,進而引發(fā)網(wǎng)絡收發(fā)延遲、調(diào)度緩慢等性能問題。
軟中斷 CPU 使用率過高也是一種最常見的性能問題。今天,我就用最常見的反向代理服務器 Nginx 的案例,教你學會分析這種情況。
案例
接下來的案例基于 Ubuntu 18.04,也同樣適用于其他的 Linux 系統(tǒng)。我使用的案例環(huán)境是這樣的:
機器配置:2 CPU、8 GB 內(nèi)存。 預先安裝 docker、sysstat、sar 、hping3、tcpdump 等工具,比如 apt-get install docker.io sysstat hping3 tcpdump。
這里我又用到了三個新工具,sar、 hping3 和 tcpdump,先簡單介紹一下:
sar 是一個系統(tǒng)活動報告工具,既可以實時查看系統(tǒng)的當前活動,又可以配置保存和報告歷史統(tǒng)計數(shù)據(jù)。 hping3 是一個可以構(gòu)造 TCP/IP 協(xié)議數(shù)據(jù)包的工具,可以對系統(tǒng)進行安全審計、防火墻測試等。 tcpdump 是一個常用的網(wǎng)絡抓包工具,常用來分析各種網(wǎng)絡問題。
本次案例用到兩臺虛擬機,我畫了一張圖來表示它們的關(guān)系。

你可以看到,其中一臺虛擬機運行 Nginx ,用來模擬待分析的 Web 服務器;而另一臺當作 Web 服務器的客戶端,用來給 Nginx 增加壓力請求。使用兩臺虛擬機的目的,是為了相互隔離,避免“交叉感染”。
接下來,我們打開兩個終端,分別 SSH 登錄到兩臺機器上,并安裝上面提到的這些工具。
同以前的案例一樣,下面的所有命令都默認以 root 用戶運行,如果你是用普通用戶身份登陸系統(tǒng),請運行 sudo su root 命令切換到 root 用戶。
操作和分析
安裝完成后,我們先在第一個終端,執(zhí)行下面的命令運行案例,也就是一個最基本的 Nginx 應用:
# 運行Nginx服務并對外開放80端口
$ docker run -itd --name=nginx -p 80:80 nginx
然后,在第二個終端,使用 curl 訪問 Nginx 監(jiān)聽的端口,確認 Nginx 正常啟動。假設 192.168.58.99 是 Nginx 所在虛擬機的 IP 地址,運行 curl 命令后你應該會看到下面這個輸出界面:
$ curl http://192.168.58.99/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
接著,還是在第二個終端,我們運行 hping3 命令,來模擬 Nginx 的客戶端請求:
# -S參數(shù)表示設置TCP協(xié)議的SYN(同步序列號),-p表示目的端口為80
# -i u100表示每隔100微秒發(fā)送一個網(wǎng)絡幀
# 注:如果你在實踐過程中現(xiàn)象不明顯,可以嘗試把100調(diào)小,比如調(diào)成10甚至1
$ hping3 -S -p 80 -i u100 192.168.58.99
現(xiàn)在我們再回到第一個終端,你應該發(fā)現(xiàn)了異常。是不是感覺系統(tǒng)響應明顯變慢了,即便只是在終端中敲幾個回車,都得很久才能得到響應?這個時候應該怎么辦呢?
雖然在運行 hping3 命令時,我就已經(jīng)告訴你,這是一個 SYN FLOOD 攻擊,你肯定也會想到從網(wǎng)絡方面入手,來分析這個問題。不過,在實際的生產(chǎn)環(huán)境中,沒人直接告訴你原因。
所以,我希望你把 hping3 模擬 SYN FLOOD 這個操作暫時忘掉,然后重新從觀察到的問題開始,分析系統(tǒng)的資源使用情況,逐步找出問題的根源。
那么,該從什么地方入手呢?剛才我們發(fā)現(xiàn),簡單的 SHELL 命令都明顯變慢了,先看看系統(tǒng)的整體資源使用情況應該是個不錯的注意,比如執(zhí)行下 top 看看是不是出現(xiàn)了 CPU 的瓶頸。我們在第一個終端運行 top 命令,看一下系統(tǒng)整體的資源使用情況。
# top運行后按數(shù)字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
...
這里你有沒有發(fā)現(xiàn)異常的現(xiàn)象?我們從第一行開始,逐個看一下:
平均負載全是 0,就緒隊列里面只有一個進程(1 running)。 每個 CPU 的使用率都挺低,最高的 CPU1 的使用率也只有 4.4%,并不算高。 再看進程列表,CPU 使用率最高的進程也只有 0.3%,還是不高呀。
那為什么系統(tǒng)的響應變慢了呢?既然每個指標的數(shù)值都不大,那我們就再來看看,這些指標對應的更具體的含義。畢竟,哪怕是同一個指標,用在系統(tǒng)的不同部位和場景上,都有可能對應著不同的性能問題。
仔細看 top 的輸出,兩個 CPU 的使用率雖然分別只有 3.3% 和 4.4%,但都用在了軟中斷上;而從進程列表上也可以看到,CPU 使用率最高的也是軟中斷進程 ksoftirqd??雌饋?,軟中斷有點可疑了。
根據(jù)上一期的內(nèi)容,既然軟中斷可能有問題,那你先要知道,究竟是哪類軟中斷的問題。停下來想想,上一節(jié)我們用了什么方法,來判斷軟中斷類型呢?沒錯,還是 proc 文件系統(tǒng)。觀察 /proc/softirqs 文件的內(nèi)容,你就能知道各種軟中斷類型的次數(shù)。
不過,這里的各類軟中斷次數(shù),又是什么時間段里的次數(shù)呢?它是系統(tǒng)運行以來的累積中斷次數(shù)。所以我們直接查看文件內(nèi)容,得到的只是累積中斷次數(shù),對這里的問題并沒有直接參考意義。因為,這些中斷次數(shù)的變化速率才是我們需要關(guān)注的。
那什么工具可以觀察命令輸出的變化情況呢?我想你應該想起來了,在前面案例中用過的 watch 命令,就可以定期運行一個命令來查看輸出;如果再加上 -d 參數(shù),還可以高亮出變化的部分,從高亮部分我們就可以直觀看出,哪些內(nèi)容變化得更快。
比如,還是在第一個終端,我們運行下面的命令:
$ 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 文件內(nèi)容的變化情況,你可以發(fā)現(xiàn), TIMER(定時中斷)、NET_RX(網(wǎng)絡接收)、SCHED(內(nèi)核調(diào)度)、RCU(RCU 鎖)等這幾個軟中斷都在不停變化。
其中,NET_RX,也就是網(wǎng)絡數(shù)據(jù)包接收軟中斷的變化速率最快。而其他幾種類型的軟中斷,是保證 Linux 調(diào)度、時鐘和臨界區(qū)保護這些正常工作所必需的,所以它們有一定的變化倒是正常的。
那么接下來,我們就從網(wǎng)絡接收的軟中斷著手,繼續(xù)分析。既然是網(wǎng)絡接收的軟中斷,第一步應該就是觀察系統(tǒng)的網(wǎng)絡接收情況。這里你可能想起了很多網(wǎng)絡工具,不過,我推薦今天的主人公工具 sar 。
sar 可以用來查看系統(tǒng)的網(wǎng)絡收發(fā)情況,還有一個好處是,不僅可以觀察網(wǎng)絡收發(fā)的吞吐量(BPS,每秒收發(fā)的字節(jié)數(shù)),還可以觀察網(wǎng)絡收發(fā)的 PPS,即每秒收發(fā)的網(wǎng)絡幀數(shù)。
我們在第一個終端中運行 sar 命令,并添加 -n DEV 參數(shù)顯示網(wǎng)絡收發(fā)的報告:
# -n DEV 表示顯示網(wǎng)絡收發(fā)的報告,間隔1秒輸出一組數(shù)據(jù)
$ 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 表示網(wǎng)卡。 第三、四列:rxpck/s 和 txpck/s 分別表示每秒接收、發(fā)送的網(wǎng)絡幀數(shù),也就是 PPS。 第五、六列:rxkB/s 和 txkB/s 分別表示每秒接收、發(fā)送的千字節(jié)數(shù),也就是 BPS。 后面的其他參數(shù)基本接近 0,顯然跟今天的問題沒有直接關(guān)系,你可以先忽略掉。
我們具體來看輸出的內(nèi)容,你可以發(fā)現(xiàn):
對網(wǎng)卡 eth0 來說,每秒接收的網(wǎng)絡幀數(shù)比較大,達到了 12607,而發(fā)送的網(wǎng)絡幀數(shù)則比較小,只有 6304;每秒接收的千字節(jié)數(shù)只有 664 KB,而發(fā)送的千字節(jié)數(shù)更小,只有 358 KB。 docker0 和 veth9f6bbcd 的數(shù)據(jù)跟 eth0 基本一致,只是發(fā)送和接收相反,發(fā)送的數(shù)據(jù)較大而接收的數(shù)據(jù)較小。這是 Linux 內(nèi)部網(wǎng)橋轉(zhuǎn)發(fā)導致的,你暫且不用深究,只要知道這是系統(tǒng)把 eth0 收到的包轉(zhuǎn)發(fā)給 Nginx 服務即可。具體工作原理,我會在后面的網(wǎng)絡部分詳細介紹。
從這些數(shù)據(jù),你有沒有發(fā)現(xiàn)什么異常的地方?
既然懷疑是網(wǎng)絡接收中斷的問題,我們還是重點來看 eth0 :接收的 PPS 比較大,達到 12607,而接收的 BPS 卻很小,只有 664 KB。直觀來看網(wǎng)絡幀應該都是比較小的,我們稍微計算一下,664*1024/12607 = 54 字節(jié),說明平均每個網(wǎng)絡幀只有 54 字節(jié),這顯然是很小的網(wǎng)絡幀,也就是我們通常所說的小包問題。
那么,有沒有辦法知道這是一個什么樣的網(wǎng)絡幀,以及從哪里發(fā)過來的呢?
使用 tcpdump 抓取 eth0 上的包就可以了。我們事先已經(jīng)知道, Nginx 監(jiān)聽在 80 端口,它所提供的 HTTP 服務是基于 TCP 協(xié)議的,所以我們可以指定 TCP 協(xié)議和 80 端口精確抓包。
接下來,我們在第一個終端中運行 tcpdump 命令,通過 -i eth0 選項指定網(wǎng)卡 eth0,并通過 tcp port 80 選項指定 TCP 協(xié)議的 80 端口:
# -i eth0 只抓取eth0網(wǎng)卡,-n不解析協(xié)議名和主機名
# tcp port 80表示只抓取tcp協(xié)議并且端口號為80的網(wǎng)絡幀
$ 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 的輸出中,你可以發(fā)現(xiàn)
192.168.0.2.18238 > 192.168.0.30.80 ,表示網(wǎng)絡幀從 192.168.0.2 的 18238 端口發(fā)送到 192.168.0.30 的 80 端口,也就是從運行 hping3 機器的 18238 端口發(fā)送網(wǎng)絡幀,目的為 Nginx 所在機器的 80 端口。 Flags [S] 則表示這是一個 SYN 包。
再加上前面用 sar 發(fā)現(xiàn)的, PPS 超過 12000 的現(xiàn)象,現(xiàn)在我們可以確認,這就是從 192.168.0.2 這個地址發(fā)送過來的 SYN FLOOD 攻擊。
到這里,我們已經(jīng)做了全套的性能診斷和分析。從系統(tǒng)的軟中斷使用率高這個現(xiàn)象出發(fā),通過觀察 /proc/softirqs 文件的變化情況,判斷出軟中斷類型是網(wǎng)絡接收中斷;再通過 sar 和 tcpdump ,確認這是一個 SYN FLOOD 問題。
SYN FLOOD 問題最簡單的解決方法,就是從交換機或者硬件防火墻中封掉來源 IP,這樣 SYN FLOOD 網(wǎng)絡幀就不會發(fā)送到服務器中。
案例結(jié)束后,也不要忘了收尾,記得停止最開始啟動的 Nginx 服務以及 hping3 命令。
在第一個終端中,運行下面的命令就可以停止 Nginx 了:
# 停止 Nginx 服務$ docker rm -f nginx
小結(jié)
軟中斷 CPU 使用率(softirq)升高是一種很常見的性能問題。雖然軟中斷的類型很多,但實際生產(chǎn)中,我們遇到的性能瓶頸大多是網(wǎng)絡收發(fā)類型的軟中斷,特別是網(wǎng)絡接收的軟中斷。
在碰到這類問題時,你可以借用 sar、tcpdump 等工具,做進一步分析。
有同學說在查看軟中斷數(shù)據(jù)時會顯示128個核的數(shù)據(jù),我的也是,雖然只有一個核,但是會顯示128個核的信息,用下面的命令可以提取有數(shù)據(jù)的核,我的1核,所以這個命令只能顯示1核,多核需要做下修改
watch -d "/bin/cat /proc/softirqs | /usr/bin/awk 'NR == 1{printf "%13s %s\\n"," ",$1}; NR > 1{printf "%13s %s\\n",$1,$2}'"
推薦閱讀
關(guān)于程序員大白
程序員大白是一群哈工大,東北大學,西湖大學和上海交通大學的碩士博士運營維護的號,大家樂于分享高質(zhì)量文章,喜歡總結(jié)知識,歡迎關(guān)注[程序員大白],大家一起學習進步!
