上一期,咱們一塊兒梳理了,網絡時不時丟包的分析定位和優化方法。先簡單回顧一下。網絡丟包,一般會帶來嚴重的性能降低,特別是對 TCP 來講,丟包一般意味着網絡擁塞和重傳,
進而會致使網絡延遲增大以及吞吐量下降。html
而分析丟包問題,仍是用咱們的老套路,從 Linux 網絡收發的流程入手,結合 TCP/IP 協議棧的原理來逐層分析。mysql
其實,在排查網絡問題時,咱們還常常碰到的一個問題,就是內核線程的 CPU 使用率很高。好比,在高併發的場景中,內核線程 ksoftirqd 的 CPU 使用率一般就會比較高。回顧一下前面學過
的 CPU 和網絡模塊,你應該知道,這是網絡收發的軟中斷致使的。linux
而要分析 ksoftirqd 這類 CPU 使用率比較高的內核線程,若是用我前面介紹過的那些分析方法,你通常須要藉助於其餘性能工具,進行輔助分析。nginx
既然要講內核線程的性能問題,在案例開始以前,咱們就先來看看,有哪些常見的內核線程。咱們知道,在 Linux 中,用戶態進程的「祖先」,都是 PID 號爲 1 的 init 進程。好比,如今主
流的 Linux 發行版中,init 都是 systemd 進程;而其餘的用戶態進程,會經過 systemd 來進行管理。git
稍微想一下 Linux 中的各類進程,除了用戶態進程外,還有大量的內核態線程。按說內核態的線程,應該先於用戶態進程啓動,但是 systemd 只管理用戶態進程。那麼,內核態線程又是誰來
管理的呢?github
實際上,Linux 在啓動過程當中,有三個特殊的進程,也就是 PID 號最小的三個進程:sql
因此,要查找內核線程,咱們只須要從 2 號進程開始,查找它的子孫進程便可。好比,你可使用 ps 命令,來查找 kthreadd 的子進程:docker
ps -f --ppid 2 -p 2 UID PID PPID C STIME TTY TIME CMD root 2 0 0 12:02 ? 00:00:01 [kthreadd] root 9 2 0 12:02 ? 00:00:21 [ksoftirqd/0] root 10 2 0 12:02 ? 00:11:47 [rcu_sched] root 11 2 0 12:02 ? 00:00:18 [migration/0] ... root 11094 2 0 14:20 ? 00:00:00 [kworker/1:0-eve] root 11647 2 0 14:27 ? 00:00:00 [kworker/0:2-cgr]
從上面的輸出,你可以看到,內核線程的名稱(CMD)都在中括號裏(這一點,咱們前面內容也有提到過)。因此,更簡單的方法,就是直接查找名稱包含中括號的進程。好比:瀏覽器
ps -ef | grep "\[.*\]" root 2 0 0 08:14 ? 00:00:00 [kthreadd] root 3 2 0 08:14 ? 00:00:00 [rcu_gp] root 4 2 0 08:14 ? 00:00:00 [rcu_par_gp] ...
瞭解內核線程的基本功能,對咱們排查問題有很是大的幫助。好比,咱們曾經在軟中斷案例中提到過 ksoftirqd。它是一個用來處理軟中斷的內核線程,而且每一個 CPU 上都有一個。bash
若是你知道了這一點,那麼,之後遇到 ksoftirqd 的 CPU 使用高的狀況,就會首先懷疑是軟中斷的問題,而後從軟中斷的角度來進一步分析。
其實,除了剛纔看到的 kthreadd 和 ksoftirqd 外,還有不少常見的內核線程,咱們在性能分析中都常常會碰到,好比下面這幾個內核線程。
瞭解這幾個容易發生性能問題的內核線程,有助於咱們更快地定位性能瓶頸。接下來,咱們來看今天的案例。
今天的案例仍是基於 Ubuntu 18.04,一樣適用於其餘的 Linux 系統。我使用的案例環境以下所示:
機器配置:2 CPU,8GB 內存。 預先安裝 docker、perf、hping三、curl 等工具,如 apt install docker.io linux-tools-common hping3。
本次案例用到兩臺虛擬機,我畫了一張圖來表示它們的關係。
你須要打開兩個終端,分別登陸這兩臺虛擬機中,並安裝上述工具。
注意,如下全部命令都默認以 root 用戶運行,若是你用普通用戶身份登錄系統,請運行 sudosu 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 u10 表示每隔 10 微秒發送一個網絡幀 # 注:若是你在實踐過程當中現象不明顯,能夠嘗試把 10 調小,好比調成 5 甚至 1 $ hping3 -S -p 80 -i u10 192.168.0.30
如今,咱們再回到第一個終端,你應該就會發現異常——系統的響應明顯變慢了。咱們不妨執行top,觀察一下系統和進程的 CPU 使用狀況:
top top - 08:31:43 up 17 min, 1 user, load average: 0.00, 0.00, 0.02 Tasks: 128 total, 1 running, 69 sleeping, 0 stopped, 0 zombie %Cpu0 : 0.3 us, 0.3 sy, 0.0 ni, 66.8 id, 0.3 wa, 0.0 hi, 32.4 si, 0.0 st %Cpu1 : 0.0 us, 0.3 sy, 0.0 ni, 65.2 id, 0.0 wa, 0.0 hi, 34.5 si, 0.0 st KiB Mem : 8167040 total, 7234236 free, 358976 used, 573828 buff/cache KiB Swap: 0 total, 0 free, 0 used. 7560460 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 9 root 20 0 0 0 0 S 7.0 0.0 0:00.48 ksoftirqd/0 18 root 20 0 0 0 0 S 6.9 0.0 0:00.56 ksoftirqd/1 2489 root 20 0 876896 38408 21520 S 0.3 0.5 0:01.50 docker-containe 3008 root 20 0 44536 3936 3304 R 0.3 0.0 0:00.09 top 1 root 20 0 78116 9000 6432 S 0.0 0.1 0:11.77 systemd ...
實際測試截圖以下:
從 top 的輸出中,你能夠看到,兩個 CPU 的軟中斷使用率都超過了 30%;而 CPU 使用率最高的進程,正好是軟中斷內核線程 ksoftirqd/0 和 ksoftirqd/1。
雖然,咱們已經知道了 ksoftirqd 的基本功能,能夠猜想是由於大量網絡收發,引發了 CPU 使用率升高;但它到底在執行什麼邏輯,咱們卻並不知道。
對於普通進程,咱們要觀察其行爲有不少方法,好比 strace、pstack、lsof 等等。但這些工具並不適合內核線程,好比,若是你用 pstack ,或者經過 /proc/pid/stack 查看 ksoftirqd/0(進程
號爲 9)的調用棧時,分別能夠獲得如下輸出:
pstack 9 Could not attach to target 9: Operation not permitted. detach: No such process
cat /proc/9/stack [<0>] smpboot_thread_fn+0x166/0x170 [<0>] kthread+0x121/0x140 [<0>] ret_from_fork+0x35/0x40 [<0>] 0xffffffffffffffff
顯然,pstack 報出的是不容許掛載進程的錯誤;而 /proc/9/stack 方式雖然有輸出,但輸出中並無詳細的調用棧狀況。
那還有沒有其餘方法,來觀察內核線程 ksoftirqd 的行爲呢?
既然是內核線程,天然應該用到內核中提供的機制。回顧一下咱們以前用過的 CPU 性能工具,我想你確定還記得 perf ,這個內核自帶的性能剖析工具。
perf 能夠對指定的進程或者事件進行採樣,而且還能夠用調用棧的形式,輸出整個調用鏈上的彙總信息。 咱們不妨就用 perf ,來試着分析一下進程號爲 9 的 ksoftirqd。
繼續在終端一中,執行下面的 perf record 命令;並指定進程號 9 ,以便記錄 ksoftirqd 的行爲:
# 採樣 30s 後退出 $ perf record -a -g -p 9 -- sleep 30
稍等一下子,在上述命令結束後,繼續執行 perf report 命令,你就能夠獲得 perf 的彙總報告。按上下方向鍵以及回車鍵,展開比例最高的 ksoftirqd 後,你就能夠獲得下面這個調用關係鏈圖:
實際測試截圖以下:
從這個圖中,你能夠清楚看到 ksoftirqd 執行最多的調用過程。雖然你可能不太熟悉內核源碼,但經過這些函數,咱們能夠大體看出它的調用棧過程。
咱們的猜想對不對呢?實際上,咱們案例最開始用 Docker 啓動了容器,而 Docker 會自動爲容器建立虛擬網卡、橋接到 docker0 網橋並配置 NAT 規則。這一過程,以下圖所示:
固然了,前面 perf report 界面的調用鏈還能夠繼續展開。但很不幸,個人屏幕不夠大,若是展開更多的層級,最後幾個層級會超出屏幕範圍。這樣,即便咱們能看到大部分的調用過程,卻也
不能說明後面層級就沒問題。
那麼,有沒有更好的方法,來查看整個調用棧的信息呢?
針對 perf 彙總數據的展現問題,Brendan Gragg 發明了火焰圖,經過矢量圖的形式,更直觀展現彙總結果。下圖就是一個針對 mysql 的火焰圖示例。
這張圖看起來像是跳動的火焰,所以也就被稱爲火焰圖。要理解火焰圖,咱們最重要的是區分清楚橫軸和縱軸的含義。
另外,要注意圖中的顏色,並無特殊含義,只是用來區分不一樣的函數。
火焰圖是動態的矢量圖格式,因此它還支持一些動態特性。好比,鼠標懸停到某個函數上時,就會自動顯示這個函數的採樣數和採樣比例。而當你用鼠標點擊函數時,火焰圖就會把該層及其上
的各層放大,方便你觀察這些處於火焰圖頂部的調用棧的細節。
上面 mysql 火焰圖的示例,就表示了 CPU 的繁忙狀況,這種火焰圖也被稱爲 on-CPU 火焰圖。
若是咱們根據性能分析的目標來劃分,火焰圖能夠分爲下面這幾種。
瞭解了火焰圖的含義和查看方法後,接下來,咱們再回到案例,運用火焰圖來觀察剛纔 perfrecord 獲得的記錄
首先,咱們須要生成火焰圖。咱們先下載幾個能從 perf record 記錄生成火焰圖的工具,這些工具都放在 https://github.com/brendangregg/FlameGraph 上面。你能夠執行下面的命令來下載:
git clone https://github.com/brendangregg/FlameGraph $ cd FlameGraph
安裝好工具後,要生成火焰圖,其實主要須要三個步驟:
1. 執行 perf script ,將 perf record 的記錄轉換成可讀的採樣記錄;
2. 執行 stackcollapse-perf.pl 腳本,合併調用棧信息;
3. 執行 flamegraph.pl 腳本,生成火焰圖。
不過,在 Linux 中,咱們可使用管道,來簡化這三個步驟的執行過程。假設剛纔用 perfrecord 生成的文件路徑爲 /root/perf.data,執行下面的命令,你就能夠直接生成火焰圖:
perf script -i /root/perf.data | ./stackcollapse-perf.pl --all | ./flamegraph.pl > ksoftirqd.svg
執行成功後,使用瀏覽器打開 ksoftirqd.svg ,你就能夠看到生成的火焰圖了。以下圖所示:
實際測試截圖以下:
根據剛剛講過的火焰圖原理,這個圖應該從下往上看,沿着調用棧中最寬的函數來分析執行次數最多的函數。這兒看到的結果,其實跟剛纔的 perf report 相似,但直觀了不少,中間這一團
火,很明顯就是最須要咱們關注的地方。
咱們順着調用棧由下往上看(順着圖中藍色箭頭),就能夠獲得跟剛纔 perf report 中同樣的結果:
不過最後,到了 ip_forward 這裏,已經看不清函數名稱了。因此咱們須要點擊 ip_forward,展開最上面這一塊調用棧:
實際測試截圖以下:
這樣,就能夠進一步看到 ip_forward 後的行爲,也就是把網絡包發送出去。根據這個調用過程,再結合咱們前面學習的網絡收發和 TCP/IP 協議棧原理,這個流程中的網絡接收、網橋以及
netfilter 調用等,都是致使軟中斷 CPU 升高的重要因素,也就是影響網絡性能的潛在瓶頸。不過,回想一下網絡收發的流程,你可能會以爲它缺了好多步驟。
好比,這個堆棧中並無 TCP 相關的調用,也沒有鏈接跟蹤 conntrack 相關的函數。實際上,這些流程都在其餘更小的火焰中,你能夠點擊上圖左上角的「Reset Zoom」,回到完整火焰圖
中,再去查看其餘小火焰的堆棧。
因此,在理解這個調用棧時要注意。從任何一個點出發、縱向來看的整個調用棧,其實只是最頂端那一個函數的調用堆棧,而非完整的內核網絡執行流程。
另外,整個火焰圖不包含任什麼時候間的因素,因此並不能看出橫向各個函數的執行次序。
到這裏,咱們就找出了內核線程 ksoftirqd 執行最頻繁的函數調用堆棧,而這個堆棧中的各層級函數,就是潛在的性能瓶頸來源。這樣,後面想要進一步分析、優化時,也就有了根據。
今天這個案例,你可能會以爲比較熟悉。實際上,這個案例,正是咱們專欄 CPU 模塊中的 軟中斷案例。
當時,咱們從軟中斷 CPU 使用率的角度入手,用網絡抓包的方法找出了瓶頸來源,確認是測試機器發送的大量 SYN 包致使的。而經過今天的 perf 和火焰圖方法,咱們進一步找出了軟中斷內
核線程的熱點函數,其實也就找出了潛在的瓶頸和優化方向。
其實,若是遇到的是內核線程的資源使用異常,不少經常使用的進程級性能工具並不能幫上忙。這時,你就能夠用內核自帶的 perf 來觀察它們的行爲,找出熱點函數,進一步定位性能瓶。當
然,perf 產生的彙總報告並不夠直觀,因此我也推薦你用火焰圖來協助排查。
實際上,火焰圖方法一樣適用於普通進程。好比,在分析 Nginx、MySQL 等各類應用場景的性能問題時,火焰圖也能幫你更快定位熱點函數,找出潛在性能問題。