Arthas | 定位線上 Dubbo 執行緒池滿異常
前言
本文是 Arthas 系列文章的第二篇。
Dubbo 執行緒池滿異常應該是大多數 Dubbo 使用者都遇到過的一個問題,本文以 Arthas 3.1.7 版本為例,介紹如何針對該異常進行診斷,主要使用到 dashboard
/thread
兩個指令。
Dubbo 執行緒池滿異常介紹
理解執行緒池滿異常需要首先了解 Dubbo 執行緒模型,官方文件:。簡單概括下 Dubbo 預設的執行緒模型:Dubbo 服務端每次接收到一個 Dubbo 請求,便交給一個執行緒池處理,該執行緒池預設有 200 個執行緒,如果 200 個執行緒都不處於空閒狀態,則
客戶端會報出如下異常:
Caused by: java.util.concurrent.ExecutionException: org.apache.dubbo.remoting.RemotingException: Server side(192.168.1.101,20880) threadpool is exhausted ...
服務端會列印 WARN 級別的日誌:
[DUBBO] Thread pool is EXHAUSTED!
引發該異常的原因主要有以下幾點:
客戶端/服務端超時時間設定不合理,導致請求無限等待,耗盡了執行緒數
客戶端請求量過大,服務端無法及時處理,耗盡了執行緒數
服務端由於 fullgc 等原因導致處理請求較慢,耗盡了執行緒數
服務端由於資料庫、Redis、網路 IO 阻塞問題,耗盡了執行緒數
...
原因可能很多,但究其根本,都是因為業務上出了問題,導致 Dubbo 執行緒池資源耗盡了。所以出現該問題,首先要做的是:
排查業務異常
緊接著針對自己的業務場景對 Dubbo 進行調優:
調整 Provider 端的 dubbo.provider.threads 引數大小,預設 200,可以適當提高。多大算合適?至少 700 不算大;不建議調的太小,容易出現上述問題
調整 Consumer 端的 dubbo.consumer.actives 引數,控制消費者呼叫的速率。這個實踐中很少使用,僅僅一提
客戶端限流
服務端擴容
Dubbo 目前不支援給某個 service 單獨配置一個隔離的執行緒池,用於保護服務,可能在以後的版本中會增加這個特性
另外,不止 Dubbo 如此設計執行緒模型,絕大多數服務治理框架、 HTTP 伺服器都有業務執行緒池的概念,所以理論上它們都會有執行緒池滿異常的可能,解決方案也類似。
那竟然問題都解釋清楚了,我們還需要排查什麼呢?一般線上上,有很多執行中的服務,這些服務都是共享一個 Dubbo 服務端執行緒池,可能因為某個服務的問題,導致整個應用被拖垮,所以需要排查是不是集中出現在某個服務上,再針對排查這個服務的業務邏輯;需要定位到執行緒堆疊,揪出導致執行緒池滿的元兇。
定位該問題,我的習慣一般是使用 Arthas 的 dashboard
和 thread
命令,而在介紹這兩個命令之前,我們先人為的構造一個 Dubbo 執行緒池滿異常的例子。
復現 Dubbo 執行緒池滿異常
配置服務端執行緒池大小
dubbo.protocol.threads=10
預設大小是 200,不利於重現該異常
模擬服務端阻塞
@Service(version = "1.0.0")
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
sleep();
return "Hello " + name;
}
private void sleep() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
sleep
方法模擬了一個耗時操作,主要是為了讓服務端執行緒池耗盡。
客戶端多執行緒訪問
for (int i = 0; i < 20; i++) {
new Thread(() -> {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
demoService.sayHello("Provider");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
問題復現
客戶端
服務端
問題得以復現,保留該現場,並假設我們並不知曉 sleep 的耗時邏輯,使用 Arthas 來進行排查。
dashboard 命令介紹
$ dashboard
執行效果
可以看到如上所示的皮膚,顯示了一些系統的執行資訊,這裡主要關注 THREAD 皮膚,介紹一下各列的含義:
ID: Java 級別的執行緒 ID,注意這個 ID 不能跟 jstack 中的 nativeID 一一對應
NAME: 執行緒名
GROUP: 執行緒組名
PRIORITY: 執行緒優先順序, 1~10 之間的數字,越大表示優先順序越高
STATE: 執行緒的狀態
CPU%: 執行緒消耗的 CPU 佔比,取樣 100ms,將所有執行緒在這 100ms 內的 CPU 使用量求和,再算出每個執行緒的 CPU 使用佔比。
TIME: 執行緒執行總時間,資料格式為
分:秒
INTERRUPTED: 執行緒當前的中斷位狀態
DAEMON: 是否是 daemon 執行緒
在空閒狀態下執行緒應該是處於 WAITING 狀態,而因為 sleep 的緣故,現在所有的執行緒均處於 TIME_WAITING 狀態,導致後來的請求被處理時,丟擲了執行緒池滿的異常。
在實際排查中,需要抽查一定數量的 Dubbo 執行緒,記錄他們的執行緒編號,看看它們到底在處理什麼服務請求。使用如下命令可以根據執行緒池名篩選出 Dubbo 服務端執行緒:
dashboard | grep "DubboServerHandler"
thread 命令介紹
使用 dashboard
篩選出個別執行緒 id 後,它的使命就完成了,剩下的操作交給 thread
命令來完成。其實,dashboard
中的 thread
模組,就是整合了 thread
命令,但是 dashboard
還可以觀察記憶體和 GC 狀態,視角更加全面,所以我個人建議,在排查問題時,先使用 dashboard
縱觀全域性資訊。
thread 使用示例:
檢視當前最忙的前 n 個執行緒
$ thread -n 3
顯示所有執行緒資訊
$ thread
和
dashboard
中顯示一致顯示當前阻塞其他執行緒的執行緒
$ thread -b
No most blocking thread found!
Affect(row-cnt:0) cost in 22 ms.這個命令還有待完善,目前只支援找出 synchronized 關鍵字阻塞住的執行緒, 如果是
java.util.concurrent.Lock
, 目前還不支援顯示指定狀態的執行緒
$ thread --state TIMED_WAITING
執行緒狀態一共有 [RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, NEW, TERMINATED] 6 種
檢視指定執行緒的執行堆疊
$ thread 46
介紹了幾種常見的用法,在實際排查中需要針對我們的現場做針對性的分析,也同時考察了我們對執行緒狀態的瞭解程度。我這裡列舉了幾種常見的執行緒狀態:
初始(NEW)
新建立了一個執行緒物件,但還沒有呼叫 start() 方法。
執行(RUNNABLE)
Java 執行緒將就緒(ready)和執行中(running)兩種狀態籠統的稱為“執行”
阻塞(BLOCKED)
執行緒阻塞於鎖
等待(WAITING)
進入該狀態的執行緒需要等待其他執行緒做出一些特定動作(通知或中斷)
Object#wait() 且不加超時引數
Thread#join() 且不加超時引數
LockSupport#park()
超時等待(TIMED_WAITING)
該狀態不同於 WAITING,它可以在指定的時間後自行返回
Thread#sleep()
Object#wait() 且加了超時引數
Thread#join() 且加了超時引數
LockSupport#parkNanos()
LockSupport#parkUntil()
終止(TERMINATED)
標識執行緒執行完畢
狀態流轉圖
問題分析
分析執行緒池滿異常並沒有通法,需要靈活變通,我們對下面這些 case 一個個分析:
阻塞類問題。例如資料庫連線不上導致卡死,執行中的執行緒基本都應該處於 BLOCKED 或者 TIMED_WAITING 狀態,我們可以藉助
thread --state
定位到繁忙類問題。例如 CPU 密集型運算,執行中的執行緒基本都處於 RUNNABLE 狀態,可以藉助於
thread -n
來定位出最繁忙的執行緒GC 類問題。很多外部因素會導致該異常,例如 GC 就是其中一個因素,這裡就不能僅僅藉助於
thread
命令來排查了。定點爆破。還記得在前面我們透過 grep 篩選出了一批 Dubbo 執行緒,可以透過
thread ${thread_id}
定向的檢視堆疊,如果統計到大量的堆疊都是一個服務時,基本可以斷定是該服務出了問題,至於說是該服務請求量突然激增,還是該服務依賴的某個下游服務突然出了問題,還是該服務訪問的資料庫斷了,那就得根據堆疊去判斷了。
總結
本文以 Dubbo 執行緒池滿異常作為引子,介紹了執行緒類問題該如何分析,以及如何透過 Arthas 快速診斷執行緒問題。有了 Arthas,基本不再需要 jstack 將 16 進位制轉來轉去了,大大提升了診斷速度。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31556476/viewspace-2675931/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 執行緒池OOM異常執行緒OOM
- JDK執行緒池異常處理方式JDK執行緒
- 執行緒池以及四種常見執行緒池執行緒
- 執行緒池異常處理的 5 中方式執行緒
- ThreadPoolTaskExecutor 如果執行緒池預設的queue滿了,會觸發什麼異常thread執行緒
- linux下定位異常消耗的執行緒實戰分析Linux執行緒
- Java併發(五)執行緒池使用番外-分析RejectedExecutionException異常Java執行緒Exception
- MySQL中介軟體之ProxySQL(5):執行緒、執行緒池、連線池MySql執行緒
- 常見的四種執行緒池執行緒
- Java執行緒池二:執行緒池原理Java執行緒
- 執行緒和執行緒池執行緒
- 執行緒 執行緒池 Task執行緒
- 多執行緒【執行緒池】執行緒
- Dubbo執行緒模型執行緒模型
- 執行緒池執行緒
- 【連載 03】Java 執行緒池(上)Java執行緒
- java執行緒池趣味事:這不是執行緒池Java執行緒
- 執行緒池運用不當的一次線上事故執行緒
- druid連線池常見異常UI
- 資料訪問連線池和執行緒池執行緒
- java--執行緒池--建立執行緒池的幾種方式與執行緒池操作詳解Java執行緒
- 二. 執行緒管理之執行緒池執行緒
- Android多執行緒之執行緒池Android執行緒
- kuangshenshuo-多執行緒-執行緒池執行緒
- 多執行緒之手撕執行緒池執行緒
- java多執行緒9:執行緒池Java執行緒
- 巧用Grafana和Arthas自動抓取K8S中異常Java程式的執行緒堆疊GrafanaK8SJava執行緒
- 執行緒池管理(1)-為什麼需要執行緒池執行緒
- Dubbo的執行緒模型執行緒模型
- Android執行緒池Android執行緒
- java 執行緒池Java執行緒
- Java執行緒池Java執行緒
- ThreadPool執行緒池thread執行緒
- 執行緒池 Executor執行緒
- 【連載 05】自定義執行緒池(上)執行緒
- 執行緒與執行緒池的那些事之執行緒池篇(萬字長文)執行緒
- 執行緒池之ScheduledThreadPoolExecutor執行緒池原始碼分析筆記執行緒thread原始碼筆記
- 執行緒池之ThreadPoolExecutor執行緒池原始碼分析筆記執行緒thread原始碼筆記