到了年末果真都不太平,最近又收到了運維報警:表示有些服務器負載很是高,讓咱們定位問題。java
還真是想什麼來什麼,前些天還故意把某些服務器的負載提升(沒錯,老闆讓我寫個 BUG!),不過還好是不一樣的環境互相沒有影響。git
拿到問題後首先去服務器上看了看,發現運行的只有咱們的 Java 應用。因而先用 ps
命令拿到了應用的 PID
。github
接着使用 top -Hp pid
將這個進程的線程顯示出來。輸入大寫的 P 能夠將線程按照 CPU 使用比例排序,因而獲得如下結果。服務器
果真某些線程的 CPU 使用率很是高。運維
爲了方便定位問題我立馬使用 jstack pid > pid.log
將線程棧 dump
到日誌文件中。函數
我在上面 100% 的線程中隨機選了一個 pid=194283
轉換爲 16 進制(2f6eb)後在線程快照中查詢:性能
由於線程快照中線程 ID 都是16進制存放。
發現這是 Disruptor
的一個堆棧,前段時間正好解決過一個因爲 Disruptor 隊列引發的一次 [OOM]():強如 Disruptor 也發生內存溢出?優化
沒想到又來一出。spa
爲了更加直觀的查看線程的狀態信息,我將快照信息上傳到專門分析的平臺上。線程
其中有一項菜單展現了全部消耗 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:
https://github.com/crossoverJie/JCSprout
你的點贊與分享是對我最大的支持