一次生產 CPU 100% 排查最佳化實踐
前言
到了年底果然都不太平,最近又收到了運維報警:表示有些伺服器負載非常高,讓我們定位問題。
還真是想什麼來什麼,前些天還故意把某些伺服器的負載提高(沒錯,老闆讓我寫個 BUG!),不過還好是不同的環境互相沒有影響。
定位問題
拿到問題後首先去伺服器上看了看,發現執行的只有我們的 Java 應用。於是先用 ps
命令拿到了應用的 PID
。
接著使用 ps-Hppid
將這個程式的執行緒顯示出來。輸入大寫的 P 可以將執行緒按照 CPU 使用比例排序,於是得到以下結果。
果然某些執行緒的 CPU 使用率非常高。
為了方便定位問題我立馬使用 jstack pid>pid.log
將執行緒棧 dump
到日誌檔案中。
我在上面 100% 的執行緒中隨機選了一個 pid=194283
轉換為 16 進位制(2f6eb)後線上程快照中查詢:
因為執行緒快照中執行緒 ID 都是16進位制存放。
發現這是 Disruptor
的一個堆疊,前段時間正好解決過一個由於 Disruptor 佇列引起的一次 OOM:強如 Disruptor 也發生記憶體溢位?
沒想到又來一出。
為了更加直觀的檢視執行緒的狀態資訊,我將快照資訊上傳到專門分析的平臺上。
其中有一項選單展示了所有消耗 CPU 的執行緒,我仔細看了下發現幾乎都是和上面的堆疊一樣。
也就是說都是 Disruptor
佇列的堆疊,同時都在執行 java.lang.Thread.yield
函式。
眾所周知 yield
函式會讓當前執行緒讓出 CPU
資源,再讓其他執行緒來競爭。
根據剛才的執行緒快照發現處於 RUNNABLE
狀態並且都在執行 yield
函式的執行緒大概有 30幾個。
因此初步判斷為大量執行緒執行 yield
函式之後互相競爭導致 CPU 使用率增高,而透過對堆疊發現是和使用 Disruptor
有關。
解決問題
而後我檢視了程式碼,發現是根據每一個業務場景在內部都會使用 2 個 Disruptor
佇列來解耦。
假設現在有 7 個業務型別,那就等於是建立 2*7=14
個 Disruptor
佇列,同時每個佇列有一個消費者,也就是總共有 14 個消費者(生產環境更多)。
同時發現配置的消費等待策略為 YieldingWaitStrategy
這種等待策略確實會執行 yield 來讓出 CPU。
程式碼如下:
初步看來和這個等待策略有很大的關係。
本地模擬
為了驗證,我在本地建立了 15 個 Disruptor
佇列同時結合監控觀察 CPU 的使用情況。
建立了 15 個 Disruptor
佇列,同時每個佇列都用執行緒池來往 Disruptor佇列
裡面傳送 100W 條資料。
消費程式僅僅只是列印一下。
跑了一段時間發現 CPU 使用率確實很高。
同時 dump
執行緒發現和生產的現象也是一致的:消費執行緒都處於 RUNNABLE
狀態,同時都在執行 yield
。
透過查詢 Disruptor
官方文件發現:
YieldingWaitStrategy 是一種充分壓榨 CPU 的策略,使用
自旋+yield
的方式來提高效能。 當消費執行緒(Event Handler threads)的數量小於 CPU 核心數時推薦使用該策略。
同時查閱到其他的等待策略 BlockingWaitStrategy
(也是預設的策略),它使用的是鎖的機制,對 CPU 的使用率不高。
於是在和之前同樣的條件下將等待策略換為 BlockingWaitStrategy
。
和剛才的 CPU 對比會發現到後面使用率的會有明顯的降低;同時 dump 執行緒後會發現大部分執行緒都處於 waiting 狀態。
最佳化解決
看樣子將等待策略換為 BlockingWaitStrategy
可以減緩 CPU 的使用,
但留意到官方對 YieldingWaitStrategy
的描述裡談道: 當消費執行緒(Event Handler threads)的數量小於 CPU 核心數時推薦使用該策略。
而現有的使用場景很明顯消費執行緒數已經大大的超過了核心 CPU 數了,因為我的使用方式是一個 Disruptor
佇列一個消費者,所以我將佇列調整為只有 1 個再試試(策略依然是 YieldingWaitStrategy
)。
跑了一分鐘,發現 CPU 的使用率一直都比較平穩而且不高。
總結
所以排查到此可以有一個結論了,想要根本解決這個問題需要將我們現有的業務拆分;現在是一個應用裡同時處理了 N 個業務,每個業務都會使用好幾個 Disruptor
佇列。
由於是在一臺伺服器上執行,所以 CPU 資源都是共享的,這就會導致 CPU 的使用率居高不下。
所以我們的調整方式如下:
為了快速緩解這個問題,先將等待策略換為
BlockingWaitStrategy
,可以有效降低 CPU 的使用率(業務上也還能接受)。第二步就需要將應用拆分(上文模擬的一個
Disruptor
佇列),一個應用處理一種業務型別;然後分別單獨部署,這樣也可以互相隔離互不影響。
當然還有其他的一些最佳化,因為這也是一個老系統了,這次 dump 執行緒居然發現建立了 800+ 的執行緒。
建立執行緒池的方式也是核心執行緒數、最大執行緒數是一樣的,導致一些空閒的執行緒也得不到回收;這樣會有很多無意義的資源消耗。
所以也會結合業務將建立執行緒池的方式調整一下,將執行緒數降下來,儘量的物盡其用。
本文的演示程式碼已上傳至 GitHub:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31556476/viewspace-2285627/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 一次生產 CPU 100% 排查優化實踐優化
- 再一次生產 CPU 高負載排查實踐負載
- 一次生產環境CPU佔用高的排查
- 一次生產環境OOM排查OOM
- Arthas 實踐——生產環境排查 CPU 飈高問題
- 一次線上CPU高的問題排查實踐
- CPU100%排查總結
- 記一次生產慢sql索引最佳化及思考SQL索引
- 生產中遇到 cpu 過高排查
- 阿里簡訊回執.net sdk的bug導致生產服務cpu 100%排查阿里
- 影片生產大映象最佳化實踐
- mysql資料庫Cpu利用率100%問題排查MySql資料庫
- 記一次獲得3倍效能的go程式最佳化實踐,及on-cpu/off-cpu火Go
- 記一次生產問題的排查,讓我領略了演算法的重要性演算法
- Java cpu 高排查Java
- 一次生產的 JVM 優化案例JVM優化
- 記 Arthas 實現一次 CPU 排查與程式碼熱更新
- 一次生產環境的docker MySQL故障DockerMySql
- 一次生產事故的優化經歷優化
- cpu飆升排查命令
- 記一次排查CPU高的問題
- 記一次生產頻繁發生FullGC問題GC
- JVM 常見線上問題 → CPU 100%、記憶體洩露 問題排查JVM記憶體洩露
- 關於 React 效能最佳化和數棧產品中的實踐React
- 線上FullGC問題排查實踐——手把手教你排查線上問題GC
- 一種KV儲存的GC最佳化實踐GC
- Golang效能最佳化實踐Golang
- 記一次生產資料庫“意外”重啟的經歷資料庫
- 一次FGC導致CPU飆高的排查過程GC
- Unity效能最佳化CPU最佳化Unity
- Java程式CPU使用率高排查Java
- 當DUBBO遇上Arthas - 排查問題的實踐
- 頁面CLS 最佳化實踐
- SAP ABAP 效能最佳化實踐
- Docker生產實踐(六)Docker
- JVM線上CPU 飈高故障排查基本操作JVM
- java應用CPU佔用率過高排查Java
- 系統執行緩慢,CPU 100%,以及Full GC次數過多問題的排查思路GC