做者 | 徐靖峯 阿里雲高級開發工程師html
Dubbo 線程池滿異常應該是大多數 Dubbo 用戶都遇到過的一個問題,本文以 Arthas 3.1.7 版本爲例,介紹如何針對該異常進行診斷,主要使用到 dashboard
/ thread
兩個指令。java
Cloud Toolkit 是阿里雲發佈的免費本地 IDE 插件,幫助開發者更高效地開發、測試、診斷並部署應用。經過插件,能夠將本地應用一鍵部署到任意服務器,甚至雲端(ECS、EDAS、ACK、ACR 和 小程序雲等);而且還內置了 Arthas 診斷、Dubbo工具、Terminal 終端、文件上傳、函數計算 和 MySQL 執行器等工具。不只僅有 IntelliJ IDEA 主流版本,還有 Eclipse、Pycharm、Maven 等其餘版本。git
理解線程池滿異常須要首先了解 Dubbo 線程模型,官方文檔:http://dubbo.apache.org/zh-cn/docs/user/demos/thread-model.html。github
簡單歸納下 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 級別的日誌:apache
[DUBBO] Thread pool is EXHAUSTED!
引起該異常的緣由主要有如下幾點:小程序
緣由可能不少,但究其根本,都是由於業務上出了問題,致使 Dubbo 線程池資源耗盡了。因此出現該問題,首先要作的是:排查業務異常。服務器
緊接着針對本身的業務場景對 Dubbo 進行調優:網絡
另外,不止 Dubbo 如此設計線程模型,絕大多數服務治理框架、 HTTP 服務器都有業務線程池的概念,因此理論上它們都會有線程池滿異常的可能,解決方案也相似。多線程
那既然問題都解釋清楚了,咱們還須要排查什麼呢?
通常在線上,有不少運行中的服務,這些服務都是共享一個 Dubbo 服務端線程池,可能由於某個服務的問題,致使整個應用被拖垮,因此須要排查是否是集中出如今某個服務上,再針對排查這個服務的業務邏輯;須要定位到線程堆棧,揪出致使線程池滿的元兇。
定位該問題,個人習慣通常是使用 Arthas 的 dashboard
和 thread
命令,而在介紹這兩個命令以前,咱們先人爲構造一個 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 面板,介紹一下各列的含義:
分:秒
在空閒狀態下線程應該是處於 WAITING 狀態,而由於 sleep 的緣故,如今全部的線程均處於 TIME_WAITING 狀態,致使後來的請求被處理時,拋出了線程池滿的異常。
在實際排查中,須要抽查必定數量的 Dubbo 線程,記錄他們的線程編號,看看它們到底在處理什麼服務請求。使用以下命令能夠根據線程池名篩選出 Dubbo 服務端線程:
dashboard | grep "DubboServerHandler"
使用 dashboard
篩選出個別線程 id 後,它的使命就完成了,剩下的操做交給 thread
命令來完成。其實,dashboard
中的 thread
模塊,就是整合了 thread
命令,可是 dashboard
還能夠觀察內存和 GC 狀態,視角更加全面,因此我我的建議,在排查問題時,先使用 dashboard
縱觀全局信息。
thread 使用示例:
$ thread -n 3
(thread -n)
$ 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
(thread --state)
線程狀態一共有 [RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, NEW, TERMINATED] 6 種。
$ thread 46
(thread ${thread_id})
介紹了幾種常見的用法,在實際排查中須要針對咱們的現場作針對性的分析,也同時考察了咱們對線程狀態的瞭解程度。我這裏列舉了幾種常見的線程狀態:
新建立了一個線程對象,但尚未調用 start() 方法。
Java 線程將就緒(ready)和運行中(running)兩種狀態籠統的稱爲「運行」。
線程阻塞於鎖。
進入該狀態的線程須要等待其餘線程作出一些特定動做(通知或中斷):
該狀態不一樣於 WAITING,它能夠在指定的時間後自行返回。
標識線程執行完畢。
(線程狀態)
分析線程池滿異常並無通法,須要靈活變通,咱們對下面這些 case 一個個分析:
thread --state
定位到;thread -n
來定位出最繁忙的線程;thread
命令來排查了;thread ${thread_id}
定向的查看堆棧,若是統計到大量的堆棧都是一個服務時,基本能夠判定是該服務出了問題,至於說是該服務請求量忽然激增,仍是該服務依賴的某個下游服務忽然出了問題,仍是該服務訪問的數據庫斷了,那就得根據堆棧去判斷了。本文以 Dubbo 線程池滿異常做爲引子,介紹了線程類問題該如何分析,以及如何經過 Arthas 快速診斷線程問題。有了 Arthas,基本再也不須要 jstack 將 16 進制轉來轉去了,大大提高了診斷速度。
Arthas 官方舉行了徵文活動,第二期徵文活動於 5 月 8 日 - 6 月 8 日舉辦,若是你有:
歡迎參加徵文活動,還有獎品拿哦~點擊瞭解詳情。