阿里妹導讀:平常工做中,咱們多少都會遇到應用的性能問題。在阿里面試中,性能優化也是常被問到的題目,用來考察是否有實際的線上問題處理經驗。面對這類問題,阿里工程師齊光給出了詳細流程。來阿里面試前,先看看這篇文章哦。
性能問題和Bug不一樣,後者的分析和解決思路更清晰,不少時候從應用日誌(文中的應用指分佈式服務下的單個節點)便可直接找到問題根源,而性能問題,其排查思路更爲複雜一些。ios
對應用進行性能優化,是一個系統性的工程,對工程師的技術廣度和技術深度都有所要求。一個簡單的應用,它不只包含了應用代碼自己,還和容器(虛擬機)、操做系統、存儲、網絡、文件系統等緊密相關,線上應用一旦出現了性能問題,須要咱們從多方面去考慮。面試
與此同時,除了一些低級的代碼邏輯引起的性能問題外,不少性能問題隱藏的較深,排查起來會比較困難,須要咱們對應用的各個子模塊、應用所使用的框架和組件的原理有所瞭解,同時掌握必定的性能優化工具和經驗。緩存
本文總結了咱們在進行性能優化時經常使用的一些工具及技巧,目的是但願經過一個全面的視角,去感知性能優化的總體脈絡。本文主要分爲下面三個部分:性能優化
本文中提到的線程、堆、垃圾回收等名詞,如無特別說明,指的是 Java 應用中的相關概念。服務器
前面提到過,應用出現性能問題和應用存在缺陷是不同的,後者大多數是因爲代碼的質量問題致使,會致使應用功能性的缺失或出現風險,一經發現,會被及時修復。而性能問題,多是由多方面的因素共同做用的結果:代碼質量通常、業務發展太快、應用架構設計不合理等,這些問題處理起來通常耗時較長、分析鏈路複雜,你們都不肯意幹,所以可能會被一些臨時性的補救手段所掩蓋,如:系統水位高或者單機的線程池隊列爆炸,那就集羣擴容增長機器;內存佔用高/高峯時段 OOM,那就重啓分分鐘解決......網絡
臨時性的補救措施只是在給應用埋雷,同時也只能解決部分問題。譬如,在不少場景下,加機器也並不能解決應用的性能問題,如對時延比較敏感的一些應用必須把單機的性能優化到極致,與此同時,加機器這種方式也形成了資源的浪費,長期來看是得不償失的。對應用進行合理的性能優化,可在應用穩定性、成本覈算得到很大的收益。架構
上面咱們闡述了進行性能優化的必要性。假設如今咱們的應用已經有了性能問題(eg. CPU 水位比較高),準備開始進行優化工做了,在這個過程當中,潛在的痛點會有哪些呢?下面列出一些較爲常見的:app
在性能優化這個領域,並無一個嚴格的流程定義,可是對於絕大多數的優化場景,咱們能夠將其過程抽象爲下面四個步驟。框架
下圖即爲上述四個階段的簡要流程。tcp
2.1 通用流程詳解
在上述通用流程的四個步驟當中,步驟2和3咱們會在接下來兩個部分重點進行介紹。首先咱們來看一下,在準備階段和測試階段,咱們須要作一些什麼。
| 2.1.1 準備階段
準備階段是很是關鍵的一步,不能省略。
首先,須要對咱們進行調優的對象進行詳盡的瞭解,所謂知己知彼,百戰不殆。
其次,咱們須要獲取基準數據,而後結合基準數據和當前的一些業務指標,肯定這次性能優化的最終目標。
| 2.1.2 測試階段
進入到這一階段,說明咱們已經初步肯定了應用性能瓶頸的所在,並且已經進行初步的調優了。檢測咱們調優是否有效的方式,就是在仿真的條件下,對應用進行壓力測試。注意:因爲 Java 有 JIT(just-in-time compilation)過程,所以壓力測試時可能須要進行前期預熱。
若是壓力測試的結果符合了預期的調優目標,或者與基準數據相比,有很大的改善,則咱們能夠繼續經過工具定位下一個瓶頸點,不然,則須要暫時排除這個瓶頸點,繼續尋找下一個變量。
2.2 注意事項
在進行性能優化時,瞭解下面這些注意事項可讓咱們少走一些彎路。
性能優化其實就是找出應用存在性能瓶頸點,而後設法經過一些調優手段去緩解。性能瓶頸點的定位是較困難的,快速、直接地定位到瓶頸點,須要具有下面兩個條件:
工欲善其事,必先利其器,咱們該如何選擇合適的工具呢?不一樣的優化場景下,又該選擇那些工具呢?
首選,咱們來看一下大名鼎鼎的「性能工具(Linux Performance Tools-full)圖」,想必不少工程師都知道,它出自系統性能專家 Brendan Gregg。該圖從 Linux 內核的各個子系統出發,列出了咱們在對各個子系統進行性能分析時,可以使用的工具,涵蓋了監測、分析、調優等性能優化的方方面面。除了這張全景圖以外,Brendan Gregg 還單獨提供了基準測試工具(Linux Performance Benchmark Tools)圖、性能監測工具(Linux Performance Observability Tools)圖等,更詳細的內容請參考 Brendan Gregg 的網站說明。
上面這張圖很是經典,是咱們作性能優化時很是好的參考資料,但事實上,咱們在實際運用的時候,會發現可能它並非最合適的,緣由主要有下面兩點:
1)對分析經驗要求較高。上面這張圖實際上是從 Linux 系統資源的角度去觀測性能指標的,這要求咱們對 Linux 各個子系統的功能、原理要有所瞭解。舉例:遇到性能問題了,咱們不會拿每一個子系統下的工具都去試一遍,大多數狀況是:咱們懷疑某個子系統有問題,而後根據這張圖上列舉的工具,去觀測或者驗證咱們的猜測,這無疑拔高了對性能優化經驗的要求;
2)適用性和完整性不是很好。咱們在分析性能問題時,從系統底層自底向上地分析是較低效的,大多數時候,從應用層面去分析會更加有效。性能工具(Linux Performance Tools-full)圖只是從系統層一個角度給出了工具集,若是從應用層開始分析,咱們可使用哪些工具?哪些點是咱們首先須要關注的?
鑑於上面若干痛點,下面給出了一張更爲實用的「性能優化工具圖譜」,該圖分別從系統層、應用層(含組件層)的角度出發,列舉了咱們在分析性能問題時首先須要關注的各項指標(其中?標註的是最須要關注的),這些點是最有可能出現性能瓶頸的地方。須要注意的是,一些低頻的指標或工具,在圖中並無列出來,如 CPU 中斷、索引節點使用、I/O事件跟蹤等,這些低頻點的排查思路較複雜,通常遇到的機會也很少,在這裏咱們聚焦最多見的一些就能夠了。
對比上面的性能工具(Linux Performance Tools-full)圖,下圖的優點在於:把具體的工具同性能指標結合了起來,同時從不一樣的層次去描述了性能瓶頸點的分佈,實用性和可操做性更強一些。系統層的工具分爲CPU、內存、磁盤(含文件系統)、網絡四個部分,工具集同性能工具(Linux Performance Tools-full)圖中的工具基本一致。組件層和應用層中的工具構成爲:JDK 提供的一些工具 + Trace 工具 + dump 分析工具 + Profiling 工具等。
這裏就不具體介紹這些工具的具體用法了,咱們可使用 man 命令獲得工具詳盡的使用說明,除此以外,還有另一個查詢命令手冊的方法:info。info 能夠理解爲 man 的詳細版本,若是 man 的輸出不太好理解,能夠去參考 info 文檔,命令太多,記不住也不必記住。
上面這張圖該如何使用?
首先,雖然從系統、組件、應用兩個三個角度去描述瓶頸點的分佈,但在實際運行時,這三者每每是相輔相成、相互影響的。系統是爲應用提供了運行時環境,性能問題的本質就是系統資源達到了使用的上限,反映在應用層,就是應用/組件的各項指標開始降低;而應用/組件的不合理使用和設計,也會加速系統資源的耗盡。所以,分析瓶頸點時,須要咱們結合從不一樣角度分析出的結果,抽出共性,獲得最終的結論。
其次,建議先從應用層入手,分析圖中標註的高頻指標,抓出最重要的、最可疑的、最有可能致使性能的點,獲得初步的結論後,再去系統層進行驗證。這樣作的好處是:不少性能瓶頸點體如今系統層,會是多變量呈現的,譬如,應用層的垃圾回收(GC)指標出現了異常,經過 JDK 自帶的工具很容易觀測到,可是體如今系統層上,會發現系統當前的 CPU 利用率、內存指標都不太正常,這就給咱們的分析思路帶來了困擾。
最後,若是瓶頸點在應用層和系統層均呈現出多變量分佈,建議此時使用 ZProfiler、JProfiler 等工具對應用進行 Profiling,獲取應用的綜合性能信息(注:Profiling 指的是在應用運行時,經過事件(Event-based)、統計抽樣(Sampling Statistical)或植入附加指令(Byte-Code instrumentation)等方法,收集應用運行時的信息,來研究應用行爲的動態分析方法)。譬如,能夠對 CPU 進行抽樣統計,結合各類符號表信息,獲得一段時間內應用內的代碼熱點。
下面介紹在不一樣的分析層次,咱們須要關注的核心性能指標,同時,也會介紹如何初步根據這些指標,判斷系統或應用是否存在性能瓶頸點,至於瓶頸點的確認、瓶頸點的成因、調優手段,將會在下一部分展開。
3.1 CPU&&線程
和 CPU 相關的指標主要有如下幾個。經常使用的工具備 top、 ps、uptime、 vmstat、 pidstat等。
top - 12:20:57 up 25 days, 20:49, 2 users, load average: 0.93, 0.97, 0.79
Tasks: 51 total, 1 running, 50 sleeping, 0 stopped, 0 zombie
%Cpu(s): 1.6 us, 1.8 sy, 0.0 ni, 89.1 id, 0.1 wa, 0.0 hi, 0.1 si, 7.3 st
KiB Mem : 8388608 total, 476436 free, 5903224 used, 2008948 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 0 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
119680 admin 20 0 600908 72332 5768 S 2.3 0.9 52:32.61 obproxy
65877 root 20 0 93528 4936 2328 S 1.3 0.1 449:03.61 alisentry_cli
第一行顯示的內容:當前時間、系統運行時間以及正在登陸用戶數。load average 後的三個數字,依次表示過去 1 分鐘、5 分鐘、15 分鐘的平均負載(Load Average)。平均負載是指單位時間內,系統處於可運行狀態(正在使用 CPU 或者正在等待 CPU 的進程,R 狀態)和不可中斷狀態(D 狀態)的平均進程數,也就是平均活躍進程數,CPU 平均負載和 CPU 使用率並無直接關係。
第三行的內容表示 CPU 利用率,每一列的含義可使用 man 查看。CPU 使用率體現了單位時間內 CPU 使用狀況的統計,以百分比的方式展現。計算方式爲:CPU 利用率 = 1 - (CPU 空閒時間)/ CPU 總的時間。須要注意的是,經過性能分析工具獲得的 CPU 的利用率實際上是某個採樣時間內的 CPU 平均值。注:top 工具顯示的的 CPU 利用率是把全部 CPU 核的數值加起來的,即 8 核 CPU 的利用率最大能夠到達800%(能夠用 htop 等更新一些的工具代替 top)。
使用 vmstat 命令,能夠查看到「上下文切換次數」這個指標,以下表所示,每隔1秒輸出1組數據:
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 504804 0 1967508 0 0 644 33377 0 1 2 2 88 0 9
上表的 cs(context switch) 就是每秒上下文切換的次數,按照不一樣場景,CPU 上下文切換還能夠分爲中斷上下文切換、線程上下文切換和進程上下文切換三種,可是不管是哪種,過多的上下文切換,都會把 CPU 時間消耗在寄存器、內核棧以及虛擬內存等數據的保存和恢復上,從而縮短進程真正運行的時間,致使系統的總體性能大幅降低。vmstat 的輸出中 us、sy 分別用戶態和內核態的 CPU 利用率,這兩個值也很是具備參考意義。
vmstat 的輸只給出了系統整體的上下文切換狀況,要想查看每一個進程的上下文切換詳情(如自願和非自願切換),須要使用 pidstat,該命令還能夠查看某個進程用戶態和內核態的 CPU 利用率。
CPU 相關指標異常的分析思路是什麼?
1)CPU 利用率:若是咱們觀察某段時間系統或應用進程的 CPU利用率一直很高(單個 core 超過80%),那麼就值得咱們警戒了。咱們能夠屢次使用 jstack 命令 dump 應用線程棧查看熱點代碼,非 Java 應用能夠直接使用 perf 進行 CPU 采采樣,離線分析採樣數據後獲得 CPU 執行熱點(Java 應用須要符號表進行堆棧信息映射,不能直接使用 perf獲得結果)。
2)CPU 平均負載:平均負載高於 CPU 數量 70%,意味着系統存在瓶頸點,形成負載升高的緣由有不少,在這裏就不展開了。須要注意的是,經過監控系統監測平均負載的變化趨勢,更容易定位問題,有時候大文件的加載等,也會致使平均負載瞬時升高。若是 1 分鐘/5 分鐘/15 分鐘的三個值相差不大,那說明系統負載很平穩,則不用關注,若是這三個值逐漸下降,說明負載在漸漸升高,須要關注總體性能;
3)CPU 上下文切換:上下文切換這個指標,並無經驗值可推薦(幾十到幾萬都有可能),這個指標值取決於系統自己的 CPU 性能,以及當前應用工做的狀況。可是,若是系統或者應用的上下文切換次數出現數量級的增加,就有很大機率說明存在性能問題,如非自願上下切換大幅度上升,說明有太多的線程在競爭 CPU。
上面這三個指標是密切相關的,如頻繁的 CPU 上下文切換,可能會致使平均負載升高。如何根據這三者之間的關係進行應用調優,將在下一部分介紹。
CPU 上的的一些異動,一般也能夠從線程上觀測到,但須要注意的是,線程問題並不徹底和 CPU 相關。與線程相關的指標,主要有下面幾個(均均可以經過 JDK 自帶的 jstack 工具直接或間接獲得):
關於線程,可關注的異常有:
1)線程總數是否過多。過多的線程,體如今 CPU 上就是致使頻繁的上下文切換,同時線程過多也會消耗內存,線程總數大小和應用自己和機器配置相關;
2)線程的狀態是否異常。觀察 WAITING/BLOCKED 線程是否過多(線程數設置過多或鎖競爭劇烈),結合應用內部鎖使用的狀況綜合分析;
3)結合 CPU 利用率,觀察是否存在大量消耗 CPU 的線程。
3.2 內存&&堆
和內存相關的指標主要有如下幾個,經常使用的分析工具備:top、free、vmstat、pidstat 以及 JDK 自帶的一些工具。
使用 free 能夠查看系統內存的使用狀況和 Swap 分區的使用狀況,top 工具能夠具體到每一個進程,如咱們能夠用使用 top 工具查看 Java 進程的常駐內存大小(RES),這兩個工具結合起來,可用覆蓋大多數內存指標。下面是使用 free命令的輸出:
$free -h
total used free shared buff/cache availableMem: 125G 6.8G 54G 2.5M 64G 118G
Swap: 2.0G 305M 1.7G
上述輸出各列的具體含義在這裏不在贅述,也比較容易理解。重點介紹下 swap 和 buff/cache 這兩個指標。
Swap 的做用是把一個本地文件或者一塊磁盤空間做爲內存來使用,包括換出和換入兩個過程。Swap 須要讀寫磁盤,因此性能不是很高,事實上,包括 ElasticSearch 、Hadoop 在內絕大部分 Java 應用都建議關掉 Swap,這是由於內存的成本一直在下降,同時這也和 JVM 的垃圾回收過程有關:JVM在 GC 的時候會遍歷全部用到的堆的內存,若是這部份內存被 Swap 出去了,遍歷的時候就會有磁盤 I/O 產生。Swap 分區的升高通常和磁盤的使用強相關,具體分析時,須要結合緩存使用狀況、swappiness 閾值以及匿名頁和文件頁的活躍狀況綜合分析。
buff/cache 是緩存和緩衝區的大小。緩存(cache):是從磁盤讀取的文件的或者向磁盤寫文件時的臨時存儲數據,面向文件。使用 cachestat 能夠查看整個系統緩存的讀寫命中狀況,使用 cachetop 能夠觀察每一個進程緩存的讀寫命中狀況。緩衝區(buffer)是寫入磁盤數據或從磁盤直接讀取的數據的臨時存儲,面向塊設備。free 命令的輸出中,這兩個指標是加在一塊兒的,使用 vmstat 命令能夠區分緩存和緩衝區,還能夠看到 Swap 分區換入和換出的內存大小。
瞭解到常見的內存指標後,常見的內存問題又有哪些?總結以下:
內存相關指標異常後,分析思路是怎麼樣的?
舉例:使用 free 發現緩存/緩衝區佔用不大,排除緩存/緩衝區對內存的影響後 -> 使用 vmstat 或者 sar 觀察一下各個進程內存使用變化趨勢 -> 發現某個進程的內存時候用持續走高 -> 若是是 Java 應用,可使用 jmap / VisualVM / heap dump 分析等工具觀察對象內存的分配,或者經過 jstat 觀察 GC 後的應用內存變化 -> 結合業務場景,定位爲內存泄漏/GC參數配置不合理/業務代碼異常等。
3.3 磁盤&&文件
在分析和磁盤相關的問題時,一般是將其和文件系統同時考慮的,下面再也不區分。和磁盤/文件系統相關的指標主要有如下幾個,經常使用的觀測工具爲 iostat和 pidstat,前者適用於整個系統,後者可觀察具體進程的 I/O。
使用 iostat 的輸出界面以下:
$iostat -dx
Linux 3.10.0-327.ali2010.alios7.x86_64 (loginhost2.alipay.em14) 10/20/2019 x86_64 (32 CPU)
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.01 15.49 0.05 8.21 3.10 240.49 58.92 0.04 4.38 2.39 4.39 0.09 0.07
上圖中 %util ,即爲磁盤 I/O 利用率,同 CPU 利用率同樣,這個值也可能超過 100%(存在並行 I/O);rkB/s 和 wkB/s分別表示每秒從磁盤讀取和寫入的數據量,即吞吐量,單位爲 KB;磁盤 I/O處理時間的指標爲 r_await 和 w_await 分別表示讀/寫請求處理完成的響應時間,svctm 表示處理 I/O 所須要的平均時間,該指標已被廢棄,無實際意義。r/s + w/s 爲 IOPS 指標,分別表示每秒發送給磁盤的讀請求數和寫請求數;aqu-sz 表示等待隊列的長度。
pidstat 的輸出大部分和 iostat 相似,區別在於它能夠實時查看每一個進程的 I/O 狀況。
如何判斷磁盤的指標出現了異常?
3.4 網絡
網絡這個概念涵蓋的範圍較廣,在應用層、傳輸層、網絡層、網絡接口層都有不一樣的指標去衡量。這裏咱們討論的「網絡」,特指應用層的網絡,一般使用的指標以下:
通常來講,應用層的網絡瓶頸有以下幾類:
帶寬和網絡吞吐這兩個指標,通常咱們會關注整個應用的,經過監控系統可直接獲得,若是一段時間內出現了明顯的指標上升,說明存在網絡性能瓶頸。對於單機,可使用 sar 獲得網絡接口、進程的網絡吞吐。
使用 ping 或者 hping3 能夠獲得是否出現網絡分區、網絡具體時延。對於應用,咱們更關注整個鏈路的時延,能夠經過中間件埋點後輸出的 trace 日誌獲得鏈路上各個環節的時延信息。
使用 netstat、ss 和 sar 能夠獲取網絡鏈接數或網絡錯誤數。過多網絡連接形成的開銷是很大的,一是會佔用文件描述符,二是會佔用緩存,所以系統能夠支撐的網絡連接數是有限的。
3.5 工具總結
能夠看到的是,在分析 CPU、內存、磁盤等的性能指標時,有幾種工具是高頻出現的,如 top、vmstat、pidstat,這裏稍微總結一下:
上述的不少工具,大部分是用於查看系統層指標的,在應用層,除了有 JDK 提供的一系列工具,一些商用的產品如 gceasy.io(分析 GC 日誌)、fastthread.io(分析線程 dump 日誌)也是不錯的。
排查 Java 應用的線上異常或者分析應用代碼瓶頸,可使用阿里開源的 Arthas ,這個工具很是強大,下面簡單介紹下。
Arthas 主要面向線上應用實時診斷,解決的是相似「線上應用異常了,須要在線進行分析和定位」的問題,固然,Arthas 提供的一些方法調用追蹤工具,對咱們排查諸如「慢查詢」等問題,也是很是有幫助的。Arthas 提供的主要功能有:
須要注意的是,性能工具只是解決性能問題的手段,咱們瞭解經常使用工具的通常用法便可,不要在工具學習上投入過多精力。
本文做者: 齊光
本文來自雲棲社區合做夥伴「阿里技術」,如需轉載請聯繫原做者。