1. 程式人生 > 其它 >Linux效能優化【轉】

Linux效能優化【轉】

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

  • 應用負載角度:直接影響了產品終端的使用者體驗
  • 系統資源角度:資源使用率、飽和度等

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

  • 選擇指標評估應用程式和系統性能
  • 為應用程式和系統設定效能目標
  • 進行效能基準測試
  • 效能分析定位瓶頸
  • 效能監控和告警

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

**平均負載:**單位時間內,系統處於可執行狀態和不可中斷狀態的平均程序數,也就是平均活躍程序數。它和我們傳統意義上理解的CPU使用率並沒有直接關係。

其中不可中斷程序是正處於核心態關鍵流程中的程序(如常見的等待裝置的I/O響應)。不可中斷狀態實際上是系統對程序和硬體裝置的一種保護機制。

實際生產環境中將系統的平均負載監控起來,根據歷史資料判斷負載的變化趨勢。當負載存在明顯升高趨勢時,及時進行分析和調查。 當然也可以當設定閾值(如當平均負載高於CPU數量的70%時)

現實工作中我們會經常混淆平均負載和CPU使用率的概念,其實兩者並不完全對等:

  • CPU密集型程序,大量CPU使用會導致平均負載升高,此時兩者一致
  • I/O密集型程序,等待I/O也會導致平均負載升高,此時CPU使用率並不一定高
  • 大量等待CPU的程序排程會導致平均負載升高,此時CPU使用率也會比較高

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

CPU上下文切換,就是把前一個任務的CPU上下文(CPU暫存器和PC)儲存起來,然後載入新任務的上下文到這些暫存器和程式計數器,最後再跳轉到程式計數器所指的位置,執行新任務。其中,儲存下來的上下文會儲存在系統核心中,待任務重新排程執行時再載入,保證原來的任務狀態不受影響。

按照任務型別,CPU上下文切換分為:

  • 程序上下文切換
  • 執行緒上下文切換
  • 中斷上下文切換

Linux程序按照等級許可權將程序的執行空間分為核心空間和使用者空間。從使用者態向核心態轉變時需要通過系統呼叫來完成。

一次系統呼叫過程其實進行了兩次CPU上下文切換:

  • CPU暫存器中使用者態的指令位置先儲存起來,CPU暫存器更新為核心態指令的位置,跳轉到核心態執行核心任務;
  • 系統呼叫結束後,CPU暫存器恢復原來儲存的使用者態資料,再切換到使用者空間繼續執行。

系統呼叫過程中並不會涉及虛擬記憶體等程序使用者態資源,也不會切換程序。和傳統意義上的程序上下文切換不同。因此系統呼叫通常稱為特權模式切換

程序是由核心管理和排程的,程序上下文切換隻能發生在核心態。 因此相比系統呼叫來說,在儲存當前程序的核心狀態和CPU暫存器之前,需要先把該程序的虛擬記憶體,棧儲存下來。再載入新程序的核心態後,還要重新整理程序的虛擬記憶體和使用者棧。

程序只有在排程到CPU上執行時才需要切換上下文,有以下幾種場景: CPU時間片輪流分配,系統資源不足導致程序掛起,程序通過sleep函式主動掛起,高優先順序程序搶佔時間片,硬體中斷時CPU上的程序被掛起轉而執行核心中的中斷服務。

執行緒上下文切換分為兩種:

  • 前後執行緒同屬於一個程序,切換時虛擬記憶體資源不變,只需要切換執行緒的私有資料,暫存器等;
  • 前後執行緒屬於不同程序,與程序上下文切換相同。

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

中斷上下文切換並不涉及到程序的使用者態,因此中斷上下文只包括核心態中斷服務程式執行所必須的狀態(CPU暫存器,核心堆疊,硬體中斷引數等)。

中斷處理優先順序比程序高,所以中斷上下文切換和程序上下文切換不會同時發生

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

  • cs (context switch) 每秒上下文切換次數
  • in (interrupt) 每秒中斷次數
  • r (runnning or runnable)就緒佇列的長度,正在執行和等待CPU的程序數
  • b (Blocked) 處於不可中斷睡眠狀態的程序數

要檢視每個程序的詳細情況,需要使用pidstat來檢視每個程序上下文切換情況

  • cswch 每秒自願上下文切換次數 (程序無法獲取所需資源導致的上下文切換)
  • nvcswch 每秒非自願上下文切換次數 (時間片輪流等系統強制排程)
1
2
3
4
5
6
7
8
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佔用率高

1
pidstat -w -u 1  #檢視到底哪個程序導致的問題

從結果中看出是sysbench導致CPU使用率過高,但是pidstat輸出的上下文次數加起來也並不多。分析sysbench模擬的是執行緒的切換,因此需要在pidstat後加-t引數檢視執行緒指標。

另外對於中斷次數過多,我們可以通過/proc/interrupts檔案讀取

1
watch -d cat /proc/interrupts

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

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 開啟呼叫關係的取樣)

1
2
3
4
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中哪個函式導致該問題。

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

發現其中sqrt和add_function佔用CPU過多, 此時檢視原始碼找到原來是sqrt中在釋出前沒有刪除測試程式碼段,存在一個百萬次的迴圈導致。 將該無用程式碼刪除後發現nginx負載能力明顯提升

1
2
3
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程序的程序號變化了,此時有可能時以下兩種原因導致:

  • 程序不停的崩潰重啟(如段錯誤/配置錯誤等),此時程序退出後可能又被監控系統重啟;
  • 短時程序導致,即其他應用內部通過exec呼叫的外面命令,這些命令一般只執行很短時間就結束,很難用top這種間隔較長的工具來發現

可以通過pstree來查詢 stress的父程序,找出呼叫關係。

1
pstree | grep stress

發現是php-fpm呼叫的該子程序,此時去檢視原始碼可以看出每個請求都會呼叫一個stress命令來模擬I/O壓力。 之前top顯示的結果是CPU使用率升高,是否真的是由該stress命令導致的,還需要繼續分析。 程式碼中給每個請求加了verbose=1的引數後可以檢視stress命令的輸出,在中斷測試該命令結果顯示stress命令執行時存在因許可權問題導致的檔案建立失敗的bug。

此時依舊只是猜測,下一步繼續通過perf工具來分析。效能報告顯示確實時stress佔用了大量的CPU,通過修復許可權問題來優化解決即可.

  • R Running/Runnable,表示程序在CPU的就緒佇列中,正在執行或者等待執行;
  • D Disk Sleep,不可中斷狀態睡眠,一般表示程序正在跟硬體互動,並且互動過程中不允許被其他程序中斷;
  • Z Zombie,殭屍程序,表示程序實際上已經結束,但是父程序還沒有回收它的資源;
  • S Interruptible Sleep,可中斷睡眠狀態,表示程序因為等待某個事件而被系統掛起,當等待事件發生則會被喚醒並進入R狀態;
  • I Idle,空閒狀態,用在不可中斷睡眠的核心執行緒上。 該狀態不會導致平均負載升高;
  • T Stop/Traced,表示程序處於暫停或跟蹤狀態(SIGSTOP/SIGCONT, GDB除錯);
  • X Dead,程序已經消亡,不會在top/ps中看到。

對於不可中斷狀態,一般都是在很短時間內結束,可忽略。但是如果系統或硬體發生故障,程序可能會保持不可中斷狀態很久,甚至系統中出現大量不可中斷狀態,此時需注意是否出現了I/O效能問題。

殭屍程序一般多程序應用容易遇到,父程序來不及處理子程序狀態時子程序就提前退出,此時子程序就變成了殭屍程序。大量的殭屍程序會用盡PID程序號,導致新程序無法建立。

1
2
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兩種資源的使用情況,便於對比分析。

1
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程序的系統呼叫。

1
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使用率

    • 使用者CPU使用率, 包括使用者態(user)和低優先順序使用者態(nice). 該指標過高說明應用程式比較繁忙.
    • 系統CPU使用率, CPU在核心態執行的時間百分比(不含中斷). 該指標高說明核心比較繁忙.
    • 等待I/O的CPU使用率, iowait, 該指標高說明系統與硬體裝置I/O互動時間比較長.
    • 軟/硬中斷CPU使用率, 該指標高說明系統中發生大量中斷.
    • steal CPU / guest CPU, 表示虛擬機器佔用的CPU百分比.
  • 平均負載

    理想情況下平均負載等於邏輯CPU個數,表示每個CPU都被充分利用. 若大於則說明系統負載較重.

  • 程序上下文切換

    包括無法獲取資源的自願切換和系統強制排程時的非自願切換. 上下文切換本身是保證Linux正常執行的一項核心功能. 過多的切換則會將原本執行程序的CPU時間消耗在暫存器,核心佔及虛擬記憶體等資料儲存和恢復上

  • CPU快取命中率

    CPU快取的複用情況,命中率越高效能越好. 其中L1/L2常用在單核,L3則用在多核中

  • 平均負載案例
    • 先用uptime檢視系統平均負載
    • 判斷負載在升高後再用mpstat和pidstat分別檢視每個CPU和每個程序CPU使用情況.找出導致平均負載較高的程序.
  • 上下文切換案例
    • 先用vmstat檢視系統上下文切換和中斷次數
    • 再用pidstat觀察程序的自願和非自願上下文切換情況
    • 最後通過pidstat觀察執行緒的上下文切換情況
  • 程序CPU使用率高案例
    • 先用top檢視系統和程序的CPU使用情況,定位到程序
    • 再用perf top觀察程序呼叫鏈,定位到具體函式
  • 系統CPU使用率高案例
    • 先用top檢視系統和程序的CPU使用情況,top/pidstat都無法找到CPU使用率高的程序
    • 重新審視top輸出
    • 從CPU使用率不高,但是處於Running狀態的程序入手
    • perf record/report發現短時程序導致 (execsnoop工具)
  • 不可中斷和殭屍程序案例
    • 先用top觀察iowait升高,發現大量不可中斷和殭屍程序
    • strace無法跟蹤程序系統呼叫
    • perf分析呼叫鏈發現根源來自磁碟直接I/O
  • 軟中斷案例
    • top觀察系統軟中斷CPU使用率高
    • 檢視/proc/softirqs找到變化速率較快的幾種軟中斷
    • sar命令發現是網路小包問題
    • tcpdump找出網路幀的型別和來源, 確定SYN FLOOD攻擊導致

根據不同的效能指標來找合適的工具:

在生產環境中往往開發者沒有許可權安裝新的工具包,只能最大化利用好系統中已經安裝好的工具. 因此要了解一些主流工具能夠提供哪些指標分析.

先執行幾個支援指標較多的工具, 如top/vmstat/pidstat,根據它們的輸出可以得出是哪種型別的效能問題. 定位到程序後再用strace/perf分析呼叫情況進一步分析. 如果是軟中斷導致用/proc/softirqs

  • 應用程式優化

    • 編譯器優化: 編譯階段開啟優化選項, 如gcc -O2
    • 演算法優化
    • 非同步處理: 避免程式因為等待某個資源而一直阻塞,提升程式的併發處理能力. (將輪詢替換為事件通知)
    • 多執行緒代替多程序: 減少上下文切換成本
    • 善用快取: 加快程式處理速度
  • 系統優化

    • CPU繫結: 將程序繫結要1個/多個CPU上,提高CPU快取命中率,減少CPU排程帶來的上下文切換
    • CPU獨佔: CPU親和性機制來分配程序
    • 優先順序調整:使用nice適當降低非核心應用的優先順序
    • 為程序設定資源顯示: cgroups設定使用上限,防止由某個應用自身問題耗盡系統資源
    • NUMA優化: CPU儘可能訪問本地記憶體
    • 中斷負載均衡: irpbalance,將中斷處理過程自動負載均衡到各個CPU上
  • TPS、QPS、系統吞吐量的區別和理解

    • QPS (Queries Per Second)每秒查詢率,一臺伺服器每秒能夠響應的查詢次數.

    • TPS (Transactions Per Second)每秒事務數,軟體測試的結果.

      • 使用者請求伺服器

      • 伺服器內部處理

      • 伺服器返回給客戶

        QPS類似TPS,但是對於一個頁面的訪問形成一個TPS,但是一次頁面請求可能包含多次對伺服器的請求,可能計入多次QPS

    • 系統吞吐量, 包括幾個重要引數:

      • QPS(TPS)

      • 併發數

      • 響應時間

        QPS(TPS)=併發數/平均相應時間

大多數計算機用的主存都是動態隨機訪問記憶體(DRAM),只有核心才可以直接訪問實體記憶體。Linux核心給每個程序提供了一個獨立的虛擬地址空間,並且這個地址空間是連續的。這樣程序就可以很方便的訪問記憶體(虛擬記憶體)。

虛擬地址空間的內部分為核心空間和使用者空間兩部分,不同字長的處理器地址空間的範圍不同。32位系統核心空間佔用1G,使用者空間佔3G。 64位系統核心空間和使用者空間都是128T,分別佔記憶體空間的最高和最低處,中間部分為未定義。

並不是所有的虛擬記憶體都會分配實體記憶體,只有實際使用的才會。分配後的實體記憶體通過記憶體對映管理。為了完成記憶體對映,核心為每個程序都維護了一個頁表,記錄虛擬地址和實體地址的對映關係。頁表實際儲存在CPU的記憶體管理單元MMU中,處理器可以直接通過硬體找出要訪問的記憶體。

當程序訪問的虛擬地址在頁表中查不到時,系統會產生一個缺頁異常,進入核心空間分配實體記憶體,更新程序頁表,再返回使用者空間恢復程序的執行。

MMU以頁為單位管理記憶體,頁大小4KB。為了解決頁表項過多問題Linux提供了多級頁表HugePage的機制。

使用者空間記憶體從低到高是五種不同的記憶體段:

  • 只讀段程式碼和常量等
  • 資料段全域性變數等
  • 動態分配的記憶體,從低地址開始向上增長
  • 檔案對映動態庫、共享記憶體等,從高地址開始向下增長
  • 包括區域性變數和函式呼叫的上下文等,棧的大小是固定的。一般8MB

malloc對應到系統呼叫上有兩種實現方式:

  • brk()針對小塊記憶體(<128K),通過移動堆頂位置來分配。記憶體釋放後不立即歸還記憶體,而是被快取起來。
  • **mmap()**針對大塊記憶體(>128K),直接用記憶體對映來分配,即在檔案對映段找一塊空閒記憶體分配。

前者的快取可以減少缺頁異常的發生,提高記憶體訪問效率。但是由於記憶體沒有歸還系統,在記憶體工作繁忙時,頻繁的記憶體分配/釋放會造成記憶體碎片。

後者在釋放時直接歸還系統,所以每次mmap都會發生缺頁異常。在記憶體工作繁忙時,頻繁記憶體分配會導致大量缺頁異常,使核心管理負擔增加。

上述兩種呼叫並沒有真正分配記憶體,這些記憶體只有在首次訪問時,才通過缺頁異常進入核心中,由核心來分配

記憶體緊張時,系統通過以下方式來回收記憶體:

  • 回收快取: LRU演算法回收最近最少使用的記憶體頁面;

  • 回收不常訪問記憶體: 把不常用的記憶體通過交換分割槽寫入磁碟

  • 殺死程序: OOM核心保護機制 (程序消耗記憶體越大oom_score越大,佔用CPU越多oom_score越小,可以通過/proc手動調整oom_adj)

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

free來檢視整個系統的記憶體使用情況

top/ps來檢視某個程序的記憶體使用情況

  • VIRT程序的虛擬記憶體大小
  • RES常駐記憶體的大小,即程序實際使用的實體記憶體大小,不包括swap和共享記憶體
  • SHR共享記憶體大小,與其他程序共享的記憶體,載入的動態連結庫以及程式程式碼段
  • %MEM程序使用實體記憶體佔系統總記憶體的百分比

buffer是對磁碟資料的快取,cache是對檔案資料的快取,它們既會用在讀請求也會用在寫請求中

快取命中率是指直接通過快取獲取資料的請求次數,佔所有請求次數的百分比。命中率越高說明快取帶來的收益越高,應用程式的效能也就越好。

安裝bcc包後可以通過cachestat和cachetop來監測快取的讀寫命中情況。

安裝pcstat後可以檢視檔案在記憶體中的快取大小以及快取比例

1
2
3
4
5
#首先安裝Go
export GOPATH=~/go
export PATH=~/go/bin:$PATH
go get golang.org/x/sys/unix
go ge github.com/tobert/pcstat/pcstat
1
2
3
4
5
6
7
8
9
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%全部快取
1
2
3
4
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標誌繞過系統快取。因此接下來觀察系統呼叫.

1
2
strace -p $(pgrep app)
#strace 結果可以看到openat開啟磁碟分割槽/dev/sdb1,傳入引數為O_RDONLY|O_DIRECT

這就解釋了為什麼讀32MB資料那麼慢,直接從磁碟讀寫肯定遠遠慢於快取。找出問題後我們再看案例的原始碼發現flags中指定了直接IO標誌。刪除該選項後重跑,驗證效能變化。

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

  • 沒正確回收分配的記憶體,導致了洩漏
  • 訪問的是已分配記憶體邊界外的地址,導致程式異常退出

虛擬記憶體分佈從低到高分別是只讀段,資料段,堆,記憶體對映段,棧五部分。其中會導致記憶體洩漏的是:

  • 堆: 由應用程式自己來分配和管理,除非程式退出這些堆記憶體不會被系統自動釋放。
  • 記憶體對映段:包括動態連結庫和共享記憶體,其中共享記憶體由程式自動分配和管理

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

預先安裝systat,docker,bcc

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

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

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

從memleak輸出可以看到,應用在不停地分配記憶體,並且這些分配的地址並沒有被回收。通過呼叫棧看到是fibonacci函式分配的記憶體沒有釋放。定位到原始碼後檢視原始碼來修復增加記憶體釋放函式即可.

系統記憶體資源緊張時通過記憶體回收和OOM殺死程序來解決。其中可回收記憶體包括:

  • 快取/緩衝區,屬於可回收資源,在檔案管理中通常叫做檔案頁
    • 被應用程式修改過暫時沒寫入磁碟的資料(髒頁),要先寫入磁碟然後才能記憶體釋放
      • 在應用程式中通過fsync將髒頁同步到磁碟
      • 交給系統,核心執行緒pdflush負責這些髒頁的重新整理
  • 記憶體對映獲取的檔案對映頁,也可以被釋放掉,下次訪問時從檔案重新讀取

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

Swap本質就是把一塊磁碟空間或者一個本地檔案當作記憶體來使用,包括換入和換出兩個過程:

  • 換出: 將程序暫時不用的記憶體資料儲存到磁碟中,並釋放這些記憶體
  • 換入: 程序再次訪問記憶體時,將它們從磁碟讀到記憶體中

Linux如何衡量記憶體資源是否緊張?

  • 直接記憶體回收新的大塊記憶體分配請求,但剩餘記憶體不足。此時系統會回收一部分記憶體;

  • kswapd0核心執行緒定期回收記憶體。為了衡量記憶體使用情況,定義了pages_min,pages_low,pages_high三個閾值,並根據其來進行記憶體的回收操作。

    • 剩餘記憶體 < pages_min,程序可用記憶體耗盡了,只有核心才可以分配記憶體

    • pages_min < 剩餘記憶體 < pages_low,記憶體壓力較大,kswapd0執行記憶體回收,直到剩餘記憶體 > pages_high

    • pages_low < 剩餘記憶體 < pages_high,記憶體有一定壓力,但可以滿足新記憶體請求

    • 剩餘記憶體 > pages_high,說明剩餘記憶體較多,無記憶體壓力

      pages_low = pages_min5 / 4 pages_high = pages_min3 / 2

很多情況下系統剩餘記憶體較多,但SWAP依舊升高,這是由於處理器的NUMA架構。

在NUMA架構下多個處理器劃分到不同的Node,每個Node都擁有自己的本地記憶體空間。在分析記憶體的使用時應該針對每個Node單獨分析

1
numactl --hardware #檢視處理器在Node的分佈情況,以及每個Node的記憶體使用情況

記憶體三個閾值可以通過/proc/zoneinfo來檢視,該檔案中還包括活躍和非活躍的匿名頁/檔案頁數。

當某個Node記憶體不足時,系統可以從其他Node尋找空閒資源,也可以從本地記憶體中回收記憶體。 通過/proc/sys/vm/zone_raclaim_mode來調整。

  • 0表示既可以從其他Node尋找空閒資源,也可以從本地回收記憶體
  • 1,2,4表示只回收本地記憶體,2表示可以會回髒資料回收記憶體,4表示可以用Swap方式回收記憶體。

在實際回收過程中Linux根據/proc/sys/vm/swapiness選項來調整使用Swap的積極程度,從0-100,數值越大越積極使用Swap,即更傾向於回收匿名頁;數值越小越消極使用Swap,即更傾向於回收檔案頁。

注意:這只是調整Swap積極程度的權重,即使設定為0,當剩餘記憶體+檔案頁小於頁高閾值時,還是會發生Swap。

說明剩餘記憶體和緩衝區的波動變化正是由於記憶體回收和快取再次分配的迴圈往復。有時候Swap用的多,有時候緩衝區波動更多。此時檢視swappiness值為60,是一個相對中和的配置,系統會根據實際執行情況來選去合適的回收型別.

系統記憶體指標

  • 已用記憶體/剩餘記憶體
  • 共享記憶體 (tmpfs實現)
  • 可用記憶體: 包括剩餘記憶體和可回收記憶體
  • 快取:磁碟讀取檔案的頁快取,slab分配器中的可回收部分
  • 緩衝區: 原始磁碟塊的臨時儲存,快取將要寫入磁碟的資料

程序記憶體指標

  • 虛擬記憶體: 5大部分
  • 常駐記憶體: 程序實際使用的實體記憶體,不包括Swap和共享記憶體
  • 共享記憶體: 與其他程序共享的記憶體,以及動態連結庫和程式的程式碼段
  • Swap記憶體: 通過Swap換出到磁碟的記憶體

缺頁異常

  • 可以直接從實體記憶體中分配,次缺頁異常
  • 需要磁碟IO介入(如Swap),主缺頁異常。 此時記憶體訪問會慢很多

根據不同的效能指標來找合適的工具:

記憶體分析工具包含的效能指標:

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

  • 先用free和top檢視系統整體記憶體使用情況
  • 再用vmstat和pidstat,檢視一段時間的趨勢,從而判斷記憶體問題的型別
  • 最後進行詳細分析,比如記憶體分配分析,快取/緩衝區分析,具體程序的記憶體使用分析等

常見的優化思路:

  • 最好禁止Swap,若必須開啟則儘量降低swappiness的值
  • 減少記憶體的動態分配,如可以用記憶體池,HugePage等
  • 儘量使用快取和緩衝區來訪問資料。如用堆疊明確宣告記憶體空間來儲存需要快取的資料,或者用Redis外部快取元件來優化資料的訪問
  • cgroups等方式來限制程序的記憶體使用情況,確保系統記憶體不被異常程序耗盡
  • /proc/pid/oom_adj調整核心應用的oom_score,保證即使記憶體緊張核心應用也不會被OOM殺死

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

pidstat主要用於監控全部或指定程序佔用系統資源的情況,如CPU,記憶體、裝置IO、任務切換、執行緒等。

使用方法:

  • pidstat –d interval times 統計各個程序的IO使用情況
  • pidstat –u interval times 統計各個程序的CPU統計資訊
  • pidstat –r interval times 統計各個程序的記憶體使用資訊
  • pidstat -w interval times 統計各個程序的上下文切換
  • p PID 指定PID

1、統計IO使用情況

  • UID
  • PID
  • kB_rd/s: 每秒程序從磁碟讀取的資料量 KB 單位 read from disk each second KB
  • kB_wr/s: 每秒程序向磁碟寫的資料量 KB 單位 write to disk each second KB
  • kB_ccwr/s: 每秒程序向磁碟寫入,但是被取消的資料量,This may occur when the task truncates some dirty pagecache.
  • iodelay: Block I/O delay, measured in clock ticks
  • Command: 程序名 task name

2、統計CPU使用情況

1
2
3
4
5
6
7
8
9
# 統計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
  • UID
  • PID
  • %usr: 程序在使用者空間佔用 cpu 的百分比
  • %system: 程序在核心空間佔用 CPU 百分比
  • %guest: 程序在虛擬機器佔用 CPU 百分比
  • %wait: 程序等待執行的百分比
  • %CPU: 程序佔用 CPU 百分比
  • CPU: 處理程序的 CPU 編號
  • Command: 程序名

3、統計記憶體使用情況

  • UID
  • PID
  • Minflt/s : 每秒次缺頁錯誤次數 (minor page faults),虛擬記憶體地址對映成實體記憶體地址產生的 page fault 次數
  • Majflt/s : 每秒主缺頁錯誤次數 (major page faults), 虛擬記憶體地址對映成實體記憶體地址時,相應 page 在 swap 中
  • VSZ virtual memory usage : 該程序使用的虛擬記憶體 KB 單位
  • RSS : 該程序使用的實體記憶體 KB 單位
  • %MEM : 記憶體使用率
  • Command : 該程序的命令 task name

4、檢視具體程序使用情況

1
2
3
4
5
6
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

轉自

Linux效能優化 - 德國粗茶淡飯
https://www.ctq6.cn/linux%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/