Linux 性能優化

作者 | mikelLam
來源 | https://www.ctq6.cn/
分享 | 運維開發故事(ID:mygsdcsf)
說明 | 文章得到作者授權才發佈!原文(https://www.ctq6.cn/linux 性能優化 /)

性能優化

性能指標

高併發和響應快對應着性能優化的兩個核心指標:吞吐延時

性能問題的本質就是系統資源已經到達瓶頸,但請求的處理還不夠快,無法支撐更多的請求。性能分析實際上就是找出應用或系統的瓶頸,設法去避免或緩解它們。

對於不同的性能問題要選取不同的性能分析工具。下面是常用的 Linux Performance Tools 以及對應分析的性能問題類型。

到底應該怎麼理解 “平均負載”

**平均負載:**單位時間內,系統處於可運行狀態和不可中斷狀態的平均進程數,也就是平均活躍進程數。它和我們傳統意義上理解的 CPU 使用率並沒有直接關係。其中不可中斷進程是正處於內核態關鍵流程中的進程(如常見的等待設備的 I/O 響應)。不可中斷狀態實際上是系統對進程和硬件設備的一種保護機制。

平均負載多少時合理

實際生產環境中將系統的平均負載監控起來,根據歷史數據判斷負載的變化趨勢。當負載存在明顯升高趨勢時,及時進行分析和調查。當然也可以當設置閾值(如當平均負載高於 CPU 數量的 70% 時) 現實工作中我們會經常混淆平均負載和 CPU 使用率的概念,其實兩者並不完全對等:

平均負載高時可能是 CPU 密集型進程導致,也可能是 I/O 繁忙導致。具體分析時可以結合 mpstat/pidstat 工具輔助分析負載來源

CPU

CPU 上下文切換 (上)

CPU 上下文切換,就是把前一個任務的 CPU 上下文(CPU 寄存器和 PC)保存起來,然後加載新任務的上下文到這些寄存器和程序計數器,最後再跳轉到程序計數器所指的位置,運行新任務。其中,保存下來的上下文會存儲在系統內核中,待任務重新調度執行時再加載,保證原來的任務狀態不受影響。按照任務類型,CPU 上下文切換分爲:

進程上下文切換

Linux 進程按照等級權限將進程的運行空間分爲內核空間和用戶空間。從用戶態向內核態轉變時需要通過系統調用來完成。一次系統調用過程其實進行了兩次 CPU 上下文切換:

系統調用過程中並不會涉及虛擬內存等進程用戶態資源,也不會切換進程。和傳統意義上的進程上下文切換不同。因此系統調用通常稱爲特權模式切換。進程是由內核管理和調度的,進程上下文切換隻能發生在內核態。因此相比系統調用來說,在保存當前進程的內核狀態和 CPU 寄存器之前,需要先把該進程的虛擬內存,棧保存下來。再加載新進程的內核態後,還要刷新進程的虛擬內存和用戶棧。進程只有在調度到 CPU 上運行時才需要切換上下文,有以下幾種場景:CPU 時間片輪流分配,系統資源不足導致進程掛起,進程通過 sleep 函數主動掛起,高優先級進程搶佔時間片,硬件中斷時 CPU 上的進程被掛起轉而執行內核中的中斷服務。

線程上下文切換

線程上下文切換分爲兩種:

同進程的線程切換消耗資源較少,這也是多線程的優勢。

中斷上下文切換

中斷上下文切換並不涉及到進程的用戶態,因此中斷上下文只包括內核態中斷服務程序執行所必須的狀態(CPU 寄存器,內核堆棧,硬件中斷參數等)。中斷處理優先級比進程高,所以中斷上下文切換和進程上下文切換不會同時發生

CPU 上下文切換 (下)

通過 vmstat 可以查看系統總體的上下文切換情況

vmstat 5         #每隔5s輸出一組數據
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 103388 145412 511056    0    0    18    60    1    1  2  1 96  0  0
 0  0      0 103388 145412 511076    0    0     0     2  450 1176  1  1 99  0  0
 0  0      0 103388 145412 511076    0    0     0     8  429 1135  1  1 98  0  0
 0  0      0 103388 145412 511076    0    0     0     0  431 1132  1  1 98  0  0
 0  0      0 103388 145412 511076    0    0     0    10  467 1195  1  1 98  0  0
 1  0      0 103388 145412 511076    0    0     0     2  426 1139  1  0 99  0  0
 4  0      0  95184 145412 511108    0    0     0    74  500 1228  4  1 94  0  0
 0  0      0 103512 145416 511076    0    0     0   455  723 1573 12  3 83  2  0

要查看每個進程的詳細情況,需要使用 pidstat 來查看每個進程上下文切換情況

pidstat -w 5
14時51分16秒   UID       PID   cswch/s nvcswch/s  Command
14時51分21秒     0         1      0.80      0.00  systemd
14時51分21秒     0         6      1.40      0.00  ksoftirqd/0
14時51分21秒     0         9     32.67      0.00  rcu_sched
14時51分21秒     0        11      0.40      0.00  watchdog/0
14時51分21秒     0        32      0.20      0.00  khugepaged
14時51分21秒     0       271      0.20      0.00  jbd2/vda1-8
14時51分21秒     0      1332      0.20      0.00  argusagent
14時51分21秒     0      5265     10.02      0.00  AliSecGuard
14時51分21秒     0      7439      7.82      0.00  kworker/0:2
14時51分21秒     0      7906      0.20      0.00  pidstat
14時51分21秒     0      8346      0.20      0.00  sshd
14時51分21秒     0     20654      9.82      0.00  AliYunDun
14時51分21秒     0     25766      0.20      0.00  kworker/u2:1
14時51分21秒     0     28603      1.00      0.00  python3
vmstat 1 1    #首先獲取空閒系統的上下文切換次數
sysbench --threads=10 --max-time=300 threads run #模擬多線程切換問題

vmstat 1 1    #新終端觀察上下文切換情況
此時發現cs數據明顯升高,同時觀察其他指標:
r列: 遠超系統CPU個數,說明存在大量CPU競爭
us和sy列: sy列佔比80%,說明CPU主要被內核佔用
in列: 中斷次數明顯上升,說明中斷處理也是潛在問題

說明運行 / 等待 CPU 的進程過多,導致大量的上下文切換,上下文切換導致系統的 CPU 佔用率高

pidstat -w -u 1  #查看到底哪個進程導致的問題

從結果中看出是 sysbench 導致 CPU 使用率過高,但是 pidstat 輸出的上下文次數加起來也並不多。分析 sysbench 模擬的是線程的切換,因此需要在 pidstat 後加 - t 參數查看線程指標。另外對於中斷次數過多,我們可以通過 / proc/interrupts 文件讀取

watch -d cat /proc/interrupts

發現次數變化速度最快的是重調度中斷(RES),該中斷用來喚醒空閒狀態的 CPU 來調度新的任務運行。分析還是因爲過多任務的調度問題,和上下文切換分析一致。

某個應用的 CPU 使用率達到 100%,怎麼辦?

Linux 作爲多任務操作系統,將 CPU 時間劃分爲很短的時間片,通過調度器輪流分配給各個任務使用。爲了維護 CPU 時間,Linux 通過事先定義的節拍率,觸發時間中斷,並使用全局變了 jiffies 記錄開機以來的節拍數。時間中斷髮生一次該值 + 1.CPU 使用率,除了空閒時間以外的其他時間佔總 CPU 時間的百分比。可以通過 / proc/stat 中的數據來計算出 CPU 使用率。因爲 / proc/stat 時開機以來的節拍數累加值,計算出來的是開機以來的平均 CPU 使用率,一般意義不大。可以間隔取一段時間的兩次值作差來計算該段時間內的平均 CPU 使用率。**性能分析工具給出的都是間隔一段時間的平均 CPU 使用率,要注意間隔時間的設置。**CPU 使用率可以通過 top 或 ps 來查看。分析進程的 CPU 問題可以通過 perf,它以性能事件採樣爲基礎,不僅可以分析系統的各種事件和內核性能,還可以用來分析指定應用程序的性能問題。perf top / perf record / perf report (-g 開啓調用關係的採樣)

sudo docker run --name nginx -p 10000:80 -itd feisky/nginx
sudo docker run --name phpfpm -itd --network container:nginx feisky/php-fpm

ab -c 10 -n 100 http://XXX.XXX.XXX.XXX:10000/ #測試Nginx服務性能

發現此時每秒可承受請求給長少,此時將測試的請求數從 100 增加到 10000。在另外一個終端運行 top 查看每個 CPU 的使用率。發現系統中幾個 php-fpm 進程導致 CPU 使用率驟升。接着用 perf 來分析具體是 php-fpm 中哪個函數導致該問題。

perf top -g -p XXXX #對某一個php-fpm進程進行分析

發現其中 sqrt 和 add_function 佔用 CPU 過多, 此時查看源碼找到原來是 sqrt 中在發佈前沒有刪除測試代碼段,存在一個百萬次的循環導致。將該無用代碼刪除後發現 nginx 負載能力明顯提升

系統的 CPU 使用率很高,爲什麼找不到高 CPU 的應用?

sudo docker run --name nginx -p 10000:80 -itd feisky/nginx:sp
sudo docker run --name phpfpm -itd --network container:nginx feisky/php-fpm:sp
ab -c 100 -n 1000 http://XXX.XXX.XXX.XXX:10000/ #併發100個請求測試

實驗結果中每秒請求數依舊不高,我們將併發請求數降爲 5 後,nginx 負載能力依舊很低。此時用 top 和 pidstat 發現系統 CPU 使用率過高,但是並沒有發現 CPU 使用率高的進程。出現這種情況一般時我們分析時遺漏的什麼信息,重新運行 top 命令並觀察一會。發現就緒隊列中處於 Running 狀態的進行過多,超過了我們的併發請求次數 5. 再仔細查看進程運行數據,發現 nginx 和 php-fpm 都處於 sleep 狀態,真正處於運行的卻是幾個 stress 進程。下一步就利用 pidstat 分析這幾個 stress 進程,發現沒有任何輸出。用 ps aux 交叉驗證發現依舊不存在該進程。說明不是工具的問題。再 top 查看發現 stress 進程的進程號變化了,此時有可能時以下兩種原因導致:

可以通過 pstree 來查找 stress 的父進程,找出調用關係。

pstree | grep stress

發現是 php-fpm 調用的該子進程,此時去查看源碼可以看出每個請求都會調用一個 stress 命令來模擬 I/O 壓力。之前 top 顯示的結果是 CPU 使用率升高,是否真的是由該 stress 命令導致的,還需要繼續分析。代碼中給每個請求加了 verbose=1 的參數後可以查看 stress 命令的輸出,在中斷測試該命令結果顯示 stress 命令運行時存在因權限問題導致的文件創建失敗的 bug。此時依舊只是猜測,下一步繼續通過 perf 工具來分析。性能報告顯示確實時 stress 佔用了大量的 CPU,通過修復權限問題來優化解決即可.

系統中出現大量不可中斷進程和殭屍進程怎麼辦?

進程狀態

對於不可中斷狀態,一般都是在很短時間內結束,可忽略。但是如果系統或硬件發生故障,進程可能會保持不可中斷狀態很久,甚至系統中出現大量不可中斷狀態,此時需注意是否出現了 I/O 性能問題。殭屍進程一般多進程應用容易遇到,父進程來不及處理子進程狀態時子進程就提前退出,此時子進程就變成了殭屍進程。大量的殭屍進程會用盡 PID 進程號,導致新進程無法建立。

磁盤 O_DIRECT 問題

sudo docker run --privileged --name=app -itd feisky/app:iowait
ps aux | grep '/app'

可以看到此時有多個 app 進程運行,狀態分別時 Ss + 和 D+。其中後面 s 表示進程是一個會話的領導進程,+ 號表示前臺進程組。其中進程組表示一組相互關聯的進程,子進程是父進程所在組的組員。會話指共享同一個控制終端的一個或多個進程組。用 top 查看系統資源發現:1)平均負載在逐漸增加,且 1 分鐘內平均負載達到了 CPU 個數,說明系統可能已經有了性能瓶頸;2)殭屍進程比較多且在不停增加;3)us 和 sys CPU 使用率都不高,iowait 卻比較高;4)每個進程 CPU 使用率也不高,但有兩個進程處於 D 狀態,可能在等待 IO。分析目前數據可知:iowait 過高導致系統平均負載升高,殭屍進程不斷增長說明有程序沒能正確清理子進程資源。用 dstat 來分析,因爲它可以同時查看 CPU 和 I/O 兩種資源的使用情況,便於對比分析。

dstat 1 10    #間隔1秒輸出10組數據

可以看到當 wai(iowait)升高時磁盤請求 read 都會很大,說明 iowait 的升高和磁盤的讀請求有關。接下來分析到底時哪個進程在讀磁盤。之前 top 查看的處於 D 狀態的進程號,用 pidstat -d -p XXX 展示進程的 I/O 統計數據。發現處於 D 狀態的進程都沒有任何讀寫操作。在用 pidstat -d 查看所有進程的 I/O 統計數據,看到 app 進程在進行磁盤讀操作,每秒讀取 32MB 的數據。進程訪問磁盤必須使用系統調用處於內核態,接下來重點就是找到 app 進程的系統調用。

sudo strace -p XXX #對app進程調用進行跟蹤

報錯沒有權限,因爲已經時 root 權限了。所以遇到這種情況,首先要檢查進程狀態是否正常。ps 命令查找該進程已經處於 Z 狀態,即殭屍進程。這種情況下 top pidstat 之類的工具無法給出更多的信息,此時像第 5 篇一樣,用 perf record -d 和 perf report 進行分析,查看 app 進程調用棧。看到 app 確實在通過系統調用 sys_read() 讀取數據,並且從 new_sync_read 和 blkdev_direct_IO 看出進程時進行直接讀操作,請求直接從磁盤讀,沒有通過緩存導致 iowait 升高。通過層層分析後,root cause 是 app 內部進行了磁盤的直接 I/O。然後定位到具體代碼位置進行優化即可。

殭屍進程

上述優化後 iowait 顯著下降,但是殭屍進程數量仍舊在增加。首先要定位殭屍進程的父進程,通過 pstree -aps XXX,打印出該殭屍進程的調用樹,發現父進程就是 app 進程。查看 app 代碼,看看子進程結束的處理是否正確(是否調用 wait()/waitpid(), 有沒有註冊 SIGCHILD 信號的處理函數等)。**碰到 iowait 升高時,先用 dstat pidstat 等工具確認是否存在磁盤 I/O 問題,再找是哪些進程導致 I/O,不能用 strace 直接分析進程調用時可以通過 perf 工具分析。**對於殭屍問題,用 pstree 找到父進程,然後看源碼檢查子進程結束的處理邏輯即可。

CPU 性能指標

性能工具

根據不同的性能指標來找合適的工具: 在生產環境中往往開發者沒有權限安裝新的工具包, 只能最大化利用好系統中已經安裝好的工具. 因此要了解一些主流工具能夠提供哪些指標分析. 先運行幾個支持指標較多的工具, 如 top/vmstat/pidstat, 根據它們的輸出可以得出是哪種類型的性能問題. 定位到進程後再用 strace/perf 分析調用情況進一步分析. 如果是軟中斷導致用 / proc/softirqs

CPU 優化

內存

Linux 內存是怎麼工作的

內存映射

大多數計算機用的主存都是動態隨機訪問內存 (DRAM),只有內核纔可以直接訪問物理內存。Linux 內核給每個進程提供了一個獨立的虛擬地址空間,並且這個地址空間是連續的。這樣進程就可以很方便的訪問內存 (虛擬內存)。虛擬地址空間的內部分爲內核空間和用戶空間兩部分,不同字長的處理器地址空間的範圍不同。32 位系統內核空間佔用 1G,用戶空間佔 3G。64 位系統內核空間和用戶空間都是 128T,分別佔內存空間的最高和最低處,中間部分爲未定義。並不是所有的虛擬內存都會分配物理內存,只有實際使用的纔會。分配後的物理內存通過內存映射管理。爲了完成內存映射,內核爲每個進程都維護了一個頁表,記錄虛擬地址和物理地址的映射關係。頁表實際存儲在 CPU 的內存管理單元 MMU 中,處理器可以直接通過硬件找出要訪問的內存。當進程訪問的虛擬地址在頁表中查不到時,系統會產生一個缺頁異常,進入內核空間分配物理內存,更新進程頁表,再返回用戶空間恢復進程的運行。MMU 以頁爲單位管理內存,頁大小 4KB。爲了解決頁表項過多問題 Linux 提供了多級頁表HugePage 的機制。

虛擬內存空間分佈

用戶空間內存從低到高是五種不同的內存段:

內存分配與回收

分配

malloc 對應到系統調用上有兩種實現方式:

前者的緩存可以減少缺頁異常的發生,提高內存訪問效率。但是由於內存沒有歸還系統,在內存工作繁忙時,頻繁的內存分配 / 釋放會造成內存碎片。後者在釋放時直接歸還系統,所以每次 mmap 都會發生缺頁異常。在內存工作繁忙時,頻繁內存分配會導致大量缺頁異常,使內核管理負擔增加。上述兩種調用並沒有真正分配內存,這些內存只有在首次訪問時,才通過缺頁異常進入內核中,由內核來分配

回收

內存緊張時,系統通過以下方式來回收內存:

echo -16 > /proc/$(pidof XXX)/oom_adj

如何查看內存使用情況

free 來查看整個系統的內存使用情況 top/ps 來查看某個進程的內存使用情況

怎樣理解內存中的 Buffer 和 Cache?

buffer 是對磁盤數據的緩存,cache 是對文件數據的緩存,它們既會用在讀請求也會用在寫請求中

如何利用系統緩存優化程序的運行效率

緩存命中率

緩存命中率是指直接通過緩存獲取數據的請求次數,佔所有請求次數的百分比。**命中率越高說明緩存帶來的收益越高,應用程序的性能也就越好。**安裝 bcc 包後可以通過 cachestat 和 cachetop 來監測緩存的讀寫命中情況。安裝 pcstat 後可以查看文件在內存中的緩存大小以及緩存比例

#首先安裝Go
export GOPATH=~/go
export PATH=~/go/bin:$PATH
go get golang.org/x/sys/unix
go ge github.com/tobert/pcstat/pcstat

dd 緩存加速

dd if=/dev/sda1 of=file bs=1M count=512 #生產一個512MB的臨時文件
echo 3 > /proc/sys/vm/drop_caches #清理緩存
pcstat file #確定剛纔生成文件不在系統緩存中,此時cached和percent都是0
cachetop 5
dd if=file of=/dev/null bs=1M #測試文件讀取速度
#此時文件讀取性能爲30+MB/s,查看cachetop結果發現並不是所有的讀都落在磁盤上,讀緩存命中率只有50%。
dd if=file of=/dev/null bs=1M #重複上述讀文件測試
#此時文件讀取性能爲4+GB/s,讀緩存命中率爲100%
pcstat file #查看文件file的緩存情況,100%全部緩存

O_DIRECT 選項繞過系統緩存

cachetop 5
sudo docker run --privileged --name=app -itd feisky/app:io-direct
sudo docker logs app #確認案例啓動成功
#實驗結果表明每讀32MB數據都要花0.9s,且cachetop輸出中顯示1024次緩存全部命中

但是憑感覺可知如果緩存命中讀速度不應如此慢,讀次數時 1024,頁大小爲 4K,五秒的時間內讀取了 1024*4KB 數據,即每秒 0.8MB,和結果中 32MB 相差較大。說明該案例沒有充分利用緩存,懷疑係統調用設置了直接 I/O 標誌繞過系統緩存。因此接下來觀察系統調用.

strace -p $(pgrep app)
#strace 結果可以看到openat打開磁盤分區/dev/sdb1,傳入參數爲O_RDONLY|O_DIRECT

這就解釋了爲什麼讀 32MB 數據那麼慢,直接從磁盤讀寫肯定遠遠慢於緩存。找出問題後我們再看案例的源代碼發現 flags 中指定了直接 IO 標誌。刪除該選項後重跑,驗證性能變化。

內存泄漏,如何定位和處理?

對應用程序來說,動態內存的分配和回收是核心又複雜的一個邏輯功能模塊。管理內存的過程中會發生各種各樣的 “事故”:

內存的分配與回收

虛擬內存分佈從低到高分別是只讀段,數據段,堆,內存映射段,棧五部分。其中會導致內存泄漏的是:

內存泄漏的危害比較大,這些忘記釋放的內存,不僅應用程序自己不能訪問,系統也不能把它們再次分配給其他應用。 內存泄漏不斷累積甚至會耗盡系統內存.

如何檢測內存泄漏

預先安裝 systat,docker,bcc

sudo docker run --name=app -itd feisky/app:mem-leak
sudo docker logs app
vmstat 3

可以看到 free 在不斷下降,buffer 和 cache 基本保持不變。說明系統的內存一致在升高。但並不能說明存在內存泄漏。此時可以通過 memleak 工具來跟蹤系統或進程的內存分配 / 釋放請求

/usr/share/bcc/tools/memleak -a -p $(pidof app)

從 memleak 輸出可以看到,應用在不停地分配內存,並且這些分配的地址並沒有被回收。通過調用棧看到是 fibonacci 函數分配的內存沒有釋放。定位到源碼後查看源碼來修復增加內存釋放函數即可.

爲什麼系統的 Swap 變高

系統內存資源緊張時通過內存回收和 OOM 殺死進程來解決。其中可回收內存包括:

對於程序自動分配的堆內存,也就是我們在內存管理中的匿名頁,雖然這些內存不能直接釋放,但是 Linux 提供了 Swap 機制將不常訪問的內存寫入到磁盤來釋放內存,再次訪問時從磁盤讀取到內存即可。

Swap 原理

Swap 本質就是把一塊磁盤空間或者一個本地文件當作內存來使用,包括換入和換出兩個過程:

Linux 如何衡量內存資源是否緊張?

NUMA 與 SWAP

很多情況下系統剩餘內存較多,但 SWAP 依舊升高,這是由於處理器的 NUMA 架構。在 NUMA 架構下多個處理器劃分到不同的 Node,每個 Node 都擁有自己的本地內存空間。在分析內存的使用時應該針對每個 Node 單獨分析

numactl --hardware #查看處理器在Node的分佈情況,以及每個Node的內存使用情況

內存三個閾值可以通過 / proc/zoneinfo 來查看,該文件中還包括活躍和非活躍的匿名頁 / 文件頁數。當某個 Node 內存不足時,系統可以從其他 Node 尋找空閒資源,也可以從本地內存中回收內存。通過 / proc/sys/vm/zone_raclaim_mode 來調整。

swappiness

在實際回收過程中 Linux 根據 / proc/sys/vm/swapiness 選項來調整使用 Swap 的積極程度,從 0-100,數值越大越積極使用 Swap,即更傾向於回收匿名頁;數值越小越消極使用 Swap,即更傾向於回收文件頁。注意:這只是調整 Swap 積極程度的權重,即使設置爲 0,當剩餘內存 + 文件頁小於頁高閾值時,還是會發生 Swap。

Swap 升高時如何定位分析

free #首先通過free查看swap使用情況,若swap=0表示未配置Swap
#先創建並開啓swap
fallocate -l 8G /mnt/swapfile
chmod 600 /mnt/swapfile
mkswap /mnt/swapfile
swapon /mnt/swapfile

free #再次執行free確保Swap配置成功

dd if=/dev/sda1 of=/dev/null bs=1G count=2048 #模擬大文件讀取
sar -r -S 1  #查看內存各個指標變化 -r內存 -S swap
#根據結果可以看出,%memused在不斷增長,剩餘內存kbmemfress不斷減少,緩衝區kbbuffers不斷增大,由此可知剩餘內存不斷分配給了緩衝區
#一段時間之後,剩餘內存很小,而緩衝區佔用了大部分內存。此時Swap使用之間增大,緩衝區和剩餘內存只在小範圍波動

停下sar命令
cachetop5 #觀察緩存
#可以看到dd進程讀寫只有50%的命中率,未命中數爲4w+頁,說明正式dd進程導致緩衝區使用升高
watch -d grep -A 15 ‘Normal’ /proc/zoneinfo #觀察內存指標變化
#發現升級內存在一個小範圍不停的波動,低於頁低閾值時會突然增大到一個大於頁高閾值的值

說明剩餘內存和緩衝區的波動變化正是由於內存回收和緩存再次分配的循環往復。有時候 Swap 用的多,有時候緩衝區波動更多。此時查看 swappiness 值爲 60,是一個相對中和的配置,系統會根據實際運行情況來選去合適的回收類型.

如何 “快準狠” 找到系統內存存在的問題

內存性能指標

系統內存指標

進程內存指標

缺頁異常

內存性能工具

根據不同的性能指標來找合適的工具: 內存分析工具包含的性能指標:

如何迅速分析內存的性能瓶頸

通常先運行幾個覆蓋面比較大的性能工具,如 free,top,vmstat,pidstat 等

常見的優化思路:

vmstat 使用詳解

vmstat 命令是最常見的 Linux/Unix 監控工具,可以展現給定時間間隔的服務器的狀態值, 包括服務器的 CPU 使用率,內存使用,虛擬內存交換情況, IO 讀寫情況。可以看到整個機器的 CPU, 內存, IO 的使用情況,而不是單單看到各個進程的 CPU 使用率和內存使用率 (使用場景不一樣)。

vmstat 2
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 1379064 282244 11537528    0    0     3   104    0    0  3  0 97  0  0
 0  0      0 1372716 282244 11537544    0    0     0    24 4893 8947  1  0 98  0  0
 0  0      0 1373404 282248 11537544    0    0     0    96 5105 9278  2  0 98  0  0
 0  0      0 1374168 282248 11537556    0    0     0     0 5001 9208  1  0 99  0  0
 0  0      0 1376948 282248 11537564    0    0     0    80 5176 9388  2  0 98  0  0
 0  0      0 1379356 282256 11537580    0    0     0   202 5474 9519  2  0 98  0  0
 1  0      0 1368376 282256 11543696    0    0     0     0 5894 8940 12  0 88  0  0
 1  0      0 1371936 282256 11539240    0    0     0 10554 6176 9481 14  1 85  1  0
 1  0      0 1366184 282260 11542292    0    0     0  7456 6102 9983  7  1 91  0  0
 1  0      0 1353040 282260 11556176    0    0     0 16924 7233 9578 18  1 80  1  0
 0  0      0 1359432 282260 11549124    0    0     0 12576 5495 9271  7  0 92  1  0
 0  0      0 1361744 282264 11549132    0    0     0    58 8606 15079  4  2 95  0  0
 1  0      0 1367120 282264 11549140    0    0     0     2 5716 9205  8  0 92  0  0
 0  0      0 1346580 282264 11562644    0    0     0    70 6416 9944 12  0 88  0  0
 0  0      0 1359164 282264 11550108    0    0     0  2922 4941 8969  3  0 97  0  0
 1  0      0 1353992 282264 11557044    0    0     0     0 6023 8917 15  0 84  0  0

# 結果說明
- r 表示運行隊列(就是說多少個進程真的分配到CPU),我測試的服務器目前CPU比較空閒,沒什麼程序在跑,當這個值超過了CPU數目,就會出現CPU瓶頸了。這個也和top的負載有關係,一般負載超過了3就比較高,超過了5就高,超過了10就不正常了,服務器的狀態很危險。top的負載類似每秒的運行隊列。如果運行隊列過大,表示你的CPU很繁忙,一般會造成CPU使用率很高。

- b 表示阻塞的進程,這個不多說,進程阻塞,大家懂的。

- swpd 虛擬內存已使用的大小,如果大於0,表示你的機器物理內存不足了,如果不是程序內存泄露的原因,那麼你該升級內存了或者把耗內存的任務遷移到其他機器。

- free   空閒的物理內存的大小,我的機器內存總共8G,剩餘3415M。

- buff   Linux/Unix系統是用來存儲,目錄裏面有什麼內容,權限等的緩存,我本機大概佔用300多M

- cache cache直接用來記憶我們打開的文件,給文件做緩衝,我本機大概佔用300多M(這裏是Linux/Unix的聰明之處,把空閒的物理內存的一部分拿來做文件和目錄的緩存,是爲了提高 程序執行的性能,當程序使用內存時,buffer/cached會很快地被使用。)

- si  每秒從磁盤讀入虛擬內存的大小,如果這個值大於0,表示物理內存不夠用或者內存泄露了,要查找耗內存進程解決掉。我的機器內存充裕,一切正常。

- so  每秒虛擬內存寫入磁盤的大小,如果這個值大於0,同上。

- bi  塊設備每秒接收的塊數量,這裏的塊設備是指系統上所有的磁盤和其他塊設備,默認塊大小是1024byte,我本機上沒什麼IO操作,所以一直是0,但是我曾在處理拷貝大量數據(2-3T)的機器上看過可以達到140000/s,磁盤寫入速度差不多140M每秒

- bo 塊設備每秒發送的塊數量,例如我們讀取文件,bo就要大於0。bi和bo一般都要接近0,不然就是IO過於頻繁,需要調整。

- in 每秒CPU的中斷次數,包括時間中斷

- cs 每秒上下文切換次數,例如我們調用系統函數,就要進行上下文切換,線程的切換,也要進程上下文切換,這個值要越小越好,太大了,要考慮調低線程或者進程的數目,例如在apache和nginx這種web服務器中,我們一般做性能測試時會進行幾千併發甚至幾萬併發的測試,選擇web服務器的進程可以由進程或者線程的峯值一直下調,壓測,直到cs到一個比較小的值,這個進程和線程數就是比較合適的值了。系統調用也是,每次調用系統函數,我們的代碼就會進入內核空間,導致上下文切換,這個是很耗資源,也要儘量避免頻繁調用系統函數。上下文切換次數過多表示你的CPU大部分浪費在上下文切換,導致CPU幹正經事的時間少了,CPU沒有充分利用,是不可取的。

- us 用戶CPU時間,我曾經在一個做加密解密很頻繁的服務器上,可以看到us接近100,r運行隊列達到80(機器在做壓力測試,性能表現不佳)。

- sy 系統CPU時間,如果太高,表示系統調用時間長,例如是IO操作頻繁。

- id 空閒CPU時間,一般來說,id + us + sy = 100,一般我認爲id是空閒CPU使用率,us是用戶CPU使用率,sy是系統CPU使用率。

- wt 等待IO CPU時間
pidstat 使用詳解

pidstat 主要用於監控全部或指定進程佔用系統資源的情況, 如 CPU, 內存、設備 IO、任務切換、線程等。使用方法:

1、統計 IO 使用情況

pidstat -d 1 10

03:02:02 PM   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
03:02:03 PM     0       816      0.00    918.81      0.00  jbd2/vda1-8
03:02:03 PM     0      1007      0.00      3.96      0.00  AliYunDun
03:02:03 PM   997      7326      0.00   1904.95    918.81  java
03:02:03 PM   997      8539      0.00      3.96      0.00  java
03:02:03 PM     0     16066      0.00     35.64      0.00  cmagent

03:02:03 PM   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
03:02:04 PM     0       816      0.00   1924.00      0.00  jbd2/vda1-8
03:02:04 PM   997      7326      0.00  11156.00   1888.00  java
03:02:04 PM   997      8539      0.00      4.00      0.00  java

2、統計 CPU 使用情況

# 統計CPU
pidstat -u 1 10
03:03:33 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
03:03:34 PM     0      2321    3.96    0.00    0.00    3.96     0  ansible
03:03:34 PM     0      7110    0.00    0.99    0.00    0.99     4  pidstat
03:03:34 PM   997      8539    0.99    0.00    0.00    0.99     5  java
03:03:34 PM   984     15517    0.99    0.00    0.00    0.99     5  java
03:03:34 PM     0     24406    0.99    0.00    0.00    0.99     5  java
03:03:34 PM     0     32158    3.96    0.00    0.00    3.96     2  ansible

3、統計內存使用情況

# 統計內存
pidstat -r 1 10
Average:      UID       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
Average:        0         1      0.20      0.00  191256   3064   0.01  systemd
Average:        0      1007      1.30      0.00  143256  22720   0.07  AliYunDun
Average:        0      6642      0.10      0.00 6301904 107680   0.33  java
Average:      997      7326     10.89      0.00 13468904 8395848  26.04  java
Average:        0      7795    348.15      0.00  108376   1233   0.00  pidstat
Average:      997      8539      0.50      0.00 8242256 2062228   6.40  java
Average:      987      9518      0.20      0.00 6300944 1242924   3.85  java
Average:        0     10280      3.70      0.00  807372   8344   0.03  aliyun-service
Average:      984     15517      0.40      0.00 6386464 1464572   4.54  java
Average:        0     16066    236.46      0.00 2678332  71020   0.22  cmagent
Average:      995     20955      0.30      0.00 6312520 1408040   4.37  java
Average:      995     20956      0.20      0.00 6093764 1505028   4.67  java
Average:        0     23936      0.10      0.00 5302416 110804   0.34  java
Average:        0     24406      0.70      0.00 10211672 2361304   7.32  java
Average:        0     26870      1.40      0.00 1470212  36084   0.11  promtail

4、查看具體進程使用情況

pidstat -T ALL -r -p 20955 1 10
03:12:16 PM   UID       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
03:12:17 PM   995     20955      0.00      0.00 6312520 1408040   4.37  java

03:12:16 PM   UID       PID minflt-nr majflt-nr  Command
03:12:17 PM   995     20955         0         0  java

最後,求關注。如果你還想看更多優質原創文章,歡迎關注我們的公衆號「運維開發故事」。

如果我的文章對你有所幫助,還請幫忙點贊、在看、轉發一下,你的支持會激勵我輸出更高質量的文章,非常感謝!

你還可以把我的公衆號設爲「星標」,這樣當公衆號文章更新時,你會在第一時間收到推送消息,避免錯過我的文章更新。

我是 喬克,《運維開發故事》公衆號團隊中的一員,一線運維農民工,雲原生實踐者,這裏不僅有硬核的技術乾貨,還有我們對技術的思考和感悟,歡迎關注我們的公衆號,期待和你一起成長!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/YV7rdeLZfvZD2A7w2VeGDw