天天看點

Arthas | 定位線上 Dubbo 線程池滿異常前言Dubbo 線程池滿異常介紹複現 Dubbo 線程池滿異常dashboard 指令介紹thread 指令介紹總結Arthas 第二期征文活動火熱進行中

Arthas | 定位線上 Dubbo 線程池滿異常前言Dubbo 線程池滿異常介紹複現 Dubbo 線程池滿異常dashboard 指令介紹thread 指令介紹總結Arthas 第二期征文活動火熱進行中

作者 | 徐靖峰  阿裡雲進階開發工程師

前言

Dubbo 線程池滿異常應該是大多數 Dubbo 使用者都遇到過的一個問題,本文以 Arthas 3.1.7 版本為例,介紹如何針對該異常進行診斷,主要使用到

dashboard

 / 

thread

兩個指令。

推薦使用 Arthas

Cloud Toolkit 是阿裡雲釋出的免費本地 IDE 插件,幫助開發者更高效地開發、測試、診斷并部署應用。通過插件,可以将本地應用一鍵部署到任意伺服器,甚至雲端(ECS、EDAS、ACK、ACR 和 小程式雲等);并且還内置了 Arthas 診斷、Dubbo工具、Terminal 終端、檔案上傳、函數計算 和 MySQL 執行器等工具。不僅僅有 IntelliJ IDEA 主流版本,還有 Eclipse、Pycharm、Maven 等其他版本。

Dubbo 線程池滿異常介紹

了解線程池滿異常需要首先了解 Dubbo 線程模型,官方文檔:

http://dubbo.apache.org/zh-cn/docs/user/demos/thread-model.html

簡單概括下 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();
}           

問題複現

用戶端

Arthas | 定位線上 Dubbo 線程池滿異常前言Dubbo 線程池滿異常介紹複現 Dubbo 線程池滿異常dashboard 指令介紹thread 指令介紹總結Arthas 第二期征文活動火熱進行中

(用戶端異常)

服務端

Arthas | 定位線上 Dubbo 線程池滿異常前言Dubbo 線程池滿異常介紹複現 Dubbo 線程池滿異常dashboard 指令介紹thread 指令介紹總結Arthas 第二期征文活動火熱進行中

(服務端異常)

問題得以複現,保留該現場,并假設我們并不知曉 sleep 的耗時邏輯,使用 Arthas 來進行排查。

dashboard 指令介紹

$ dashboard           

執行效果:

Arthas | 定位線上 Dubbo 線程池滿異常前言Dubbo 線程池滿異常介紹複現 Dubbo 線程池滿異常dashboard 指令介紹thread 指令介紹總結Arthas 第二期征文活動火熱進行中

(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           
Arthas | 定位線上 Dubbo 線程池滿異常前言Dubbo 線程池滿異常介紹複現 Dubbo 線程池滿異常dashboard 指令介紹thread 指令介紹總結Arthas 第二期征文活動火熱進行中

(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           
Arthas | 定位線上 Dubbo 線程池滿異常前言Dubbo 線程池滿異常介紹複現 Dubbo 線程池滿異常dashboard 指令介紹thread 指令介紹總結Arthas 第二期征文活動火熱進行中

(thread --state)

線程狀态一共有 [RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, NEW, TERMINATED] 6 種。

  • 檢視指定線程的運作堆棧
$ thread 46           
Arthas | 定位線上 Dubbo 線程池滿異常前言Dubbo 線程池滿異常介紹複現 Dubbo 線程池滿異常dashboard 指令介紹thread 指令介紹總結Arthas 第二期征文活動火熱進行中

(thread ${thread_id})

介紹了幾種常見的用法,在實際排查中需要針對我們的現場做針對性的分析,也同時考察了我們對線程狀态的了解程度。我這裡列舉了幾種常見的線程狀态:

  • 初始(NEW)

新建立了一個線程對象,但還沒有調用 start() 方法。

  • 運作(RUNNABLE)

Java 線程将就緒(ready)和運作中(running)兩種狀态籠統的稱為“運作”。

  • 阻塞(BLOCKED)

線程阻塞于鎖。

  • 等待(WAITING)

進入該狀态的線程需要等待其他線程做出一些特定動作(通知或中斷):

  1. Object#wait() 且不加逾時參數
  2. Thread#join() 且不加逾時參數
  3. LockSupport#park()
  • 逾時等待(TIMED_WAITING)

該狀态不同于 WAITING,它可以在指定的時間後自行傳回。

  1. Thread#sleep()
  2. Object#wait() 且加了逾時參數
  3. Thread#join() 且加了逾時參數
  4. LockSupport#parkNanos()
  5. LockSupport#parkUntil()‘
  • 終止(TERMINATED)

辨別線程執行完畢。

狀态流轉圖

Arthas | 定位線上 Dubbo 線程池滿異常前言Dubbo 線程池滿異常介紹複現 Dubbo 線程池滿異常dashboard 指令介紹thread 指令介紹總結Arthas 第二期征文活動火熱進行中

(線程狀态)

問題分析

分析線程池滿異常并沒有通法,需要靈活變通,我們對下面這些 case 一個個分析:

  • 阻塞類問題。例如資料庫連接配接不上導緻卡死,運作中的線程基本都應該處于 BLOCKED 或者 TIMED_WAITING 狀态,我們可以借助

    thread --state

    定位到;
  • 繁忙類問題。例如 CPU 密集型運算,運作中的線程基本都處于 RUNNABLE 狀态,可以借助于

    thread -n

    來定位出最繁忙的線程;
  • GC 類問題。很多外部因素會導緻該異常,例如 GC 就是其中一個因素,這裡就不能僅僅借助于

    thread

    指令來排查了;
  • 定點爆破。還記得在前面我們通過 grep 篩選出了一批 Dubbo 線程,可以通過

    thread ${thread_id}

    定向的檢視堆棧,如果統計到大量的堆棧都是一個服務時,基本可以斷定是該服務出了問題,至于說是該服務請求量突然激增,還是該服務依賴的某個下遊服務突然出了問題,還是該服務通路的資料庫斷了,那就得根據堆棧去判斷了。

總結

本文以 Dubbo 線程池滿異常作為引子,介紹了線程類問題該如何分析,以及如何通過 Arthas 快速診斷線程問題。有了 Arthas,基本不再需要 jstack 将 16 進制轉來轉去了,大大提升了診斷速度。

Arthas 第二期征文活動火熱進行中

Arthas 官方舉行了征文活動,第二期征文活動于 5 月 8 日 - 6 月 8 日舉辦,如果你有:

  • 使用 Arthas 排查過的問題
  • 對 Arthas 進行源碼解讀
  • 對 Arthas 提出建議
  • 不限,其它與 Arthas 有關的内容

 歡迎參加征文活動,還有獎品拿哦~

點選了解詳情