效能優化的過程學習

CodersCoder發表於2020-11-07

背景

即使熟悉了開發中的各項技術和優化技巧,但在真正的效能優化場景下,自己依舊很難開展優化任務。所以需要總結一些優化方案,方便以後查閱和學習。

如何找到優化目標?

應用效能低,有很多方面的因素,比如業務需求層面、架構設計層面、硬體/軟體層面等。

通常,關注一個硬體資源(比如 CPU),我們主要關注以下基本要素。

利用率: 一般是瞬時值,屬於取樣範圍,用來判斷有沒有峰值,比如 CPU 使用率。
飽和度: 一般指資源是否被合理利用,能否用分擔更多的工作。比如,飽和度過高,新請求在特定 queue 裡排隊;再比如,記憶體利用率過低、CPU 利用率過高,就可以考慮空間換時間。
錯誤資訊: 錯誤一般發生在問題嚴重的情況下,需要特別關注。
聯想資訊: 對引起的原因進行猜測,並用更多的工具驗證猜想,猜測影響因素並不一定是準確的,只是幫助我們分析問題,比如系統響應慢很可能是大量使用了 SWAP 導致的。

CPU
檢視 CPU 使用可以使用 top 命令,尤其注意它的負載(load)和使用率,vmstat 命令也可以看到系統的一些執行狀況。
記憶體
記憶體可以使用 free 命令檢視,尤其關注剩餘記憶體的大小(free)。對於 Linux 系統來說,啟動之後由於各種快取和緩衝區的原因,系統記憶體會被迅速佔滿,所以我們更加關注的是 JVM 的記憶體。
top 命令的 RES 列,顯示的就是程式實際佔用的實體記憶體,這個值通常比 jmap 命令獲取的堆記憶體要大,因為它還包含大量的堆外記憶體空間。
網路
iotop 可以看到佔用網路流量最高的程式;通過 netstat 命令或者 ss 命令,能夠看到當前機器上的網路連線彙總。在一些較底層的優化中,會涉及針對 mtu 的網路優化。
I/O
通過 iostat 命令,可以檢視磁碟 I/O 的使用情況,如果利用率過高,就需要從使用源頭找原因;類似 iftop,iotop 可以檢視佔用 I/O 最多的程式,很容易可以找到優化目標。
其他
lsof 命令可以檢視當前程式所關聯的所有資源;sysctl 命令可以檢視當前系統核心的配置引數; dmesg 命令可以顯示系統級別的一些資訊,比如被作業系統的 oom-killer 殺掉的程式就可以在這裡找到。
參考:在這裡插入圖片描述

解決

找到了具體的效能瓶頸點,就可以針對性地進行優化。
1.CPU 問題
CPU 是系統的核心資源,如果 CPU 有瓶頸,很多工和執行緒就獲取不到時間片,便會執行緩慢。如果此時系統的記憶體充足,就要考慮是否可以空間換時間,通過資料冗餘和更優的演算法來減少 CPU 的使用。
在 Linux 系統上,通過 top-Hp 便能容易地獲取佔用 CPU 最高的執行緒,進行鍼對性的優化。
資源的使用要細分,才能夠進行專項優化。
我曾經碰見一個棘手的效能問題,執行緒都阻塞在 ForkJoin 執行緒池上,經過仔細排查才分析出,程式碼在等待耗時的 I/O 時,採用了並行流(parallelStrea)處理,但是 Java 預設的方式是所有使用並行流的地方,公用了一個通用的執行緒池,這個執行緒池的並行度只有 CPU 的兩倍。所以請求量一增加,任務就會排隊,造成積壓。
2.記憶體問題
記憶體問題通常是 OOM 問題,可以參考“19 | 高階進階:JVM 常見優化引數”進行優化。如果記憶體資源很緊張,CPU 利用率低,則可以考慮時間換空間的方式。
SWAP 分割槽使用硬碟來擴充套件可用記憶體的大小,但它的速度非常慢。一般在高併發的應用中,會把 SWAP 關掉,因為它很容易會引起卡頓。
3.I/O 問題
我們通常開發的業務系統,磁碟 I/O 負載都比較小,但網路 I/O 都比較繁忙。
當遇到磁碟 I/O 佔用高的情況,就要考慮是否是日誌列印得太多導致的。通過調整日誌級別,或者清理無用的日誌程式碼,便可緩解磁碟 I/O 的壓力。
業務系統還會有大量的網路 I/O 操作,比如通過 RPC 呼叫一個遠端的服務,我們期望使用 NIO 來減少一些無效的等待,或者使用並行來加快資訊的獲取。
還有一種情況,是類似於 ES 這樣的資料庫應用,資料寫入本身,就會造成繁重的磁碟 I/O。這個時候,可以增加硬體的配置,比如換成 SSD 磁碟,或者增加新的磁碟。
資料庫服務本身,也會提供非常多的引數,用來調優效能。根據“06 | 案例分析:緩衝區如何讓程式碼加速”和“07 | 案例分析:無處不在的快取,高併發系統的法寶”的描述,這部分的配置引數,主要影響緩衝和快取的行為。
比如 ES 的 segment 塊大小,translog 的重新整理速度等,都可以被微調。舉個例子,大量日誌寫入 ES 的時候,就可以通過增大 translog 寫盤的間隔,來獲得較大的效能提升。
4.網路問題
資料包在網路上傳輸,影響的主要因素就是結果集的大小。通過去除無用的資訊,啟用合理的壓縮,可以獲得較大的效能提升。
值得注意的是,這裡的網路傳輸值得不僅僅是針對瀏覽器的,在服務間呼叫中也有著同樣的情況。
比如,在 SpringBoot 的配置檔案中,通過配置下面的引數,就可以開啟 gzip。

server:

  compression:

    enabled: true

    min-response-size: 1024

    mime-types: ["text/html","application/json","application/octet-stream"]

但是,這個 SpringBoot 服務,通過 Feign 介面從另外一個服務獲取資訊,這個結果集並沒有被壓縮。
可以通過替換 Feign 的底層網路工具為 OkHTTP,使用 OkHTTP 的透明壓縮(預設開啟 gzip),即可完成服務間呼叫的資訊壓縮,但很多同學容易忘掉這一環。我曾經調優果一個專案,將返回的資料包從9MB 壓縮到300KB 左右,極大地減少了網路傳輸,節省了大約 500ms 的時間。
網路 I/O 的另外一個問題就是頻繁的網路互動,通過將結果集合並,使用批量的方式,可以顯著增加效能,但這種方式的使用場景有限,比較適合非同步的任務處理。
使用 netstat 命令,或者 lsof 命令,可以獲取程式所關聯的,TIME_WAIT 和 CLOSE_WAIT 網路狀態的數量,前者可以通過調整核心引數來解決,但後者多是應用程式的 BUG。

參考:
在這裡插入圖片描述

以上內容學習自拉勾教育,為方便後續問題排查,簡單記錄下~

相關文章