用了很多年Dubbo,連Dubbo執行緒池監控都不知道,覺得自己很厲害?

猿天地發表於2021-02-07

前言

micrometer中自帶了很多其他框架的指標資訊,可以很方便的通過prometheus進行採集和監控,常用的有JVM的資訊,Http請求的資訊,Tomcat執行緒的資訊等。

對於一些比較活躍的框架,有些還是不支援的,比如Dubbo。如果想監控Dubbo的一些指標,比如執行緒池的狀況,我們需要手動去擴充套件,輸出對應的執行緒池指標才行。

在這種情況下,肯定是沒什麼思路的,因為你不知道怎麼去擴充套件,下面給大家介紹去做一件事情之前的思考,方式方法很重要。

  • Dubbo有沒有現成的實現?
  • 參考micrometer中指標的實現,依葫蘆畫瓢?

Dubbo有沒有現成的實現?

完整的實現應該沒有,至少我還沒用過,也沒有那種去搜尋引擎一搜就大把結果的現狀,於是我在Dubbo的Github上找到了一個相關的專案dubbo-spring-boot-actuator。

https://github.com/apache/dubbo-spring-boot-project/tree/master/dubbo-spring-boot-actuator

dubbo-spring-boot-actuator看名稱就知道,提供了Dubbo相關的各種資訊端點和健康檢查。從這裡面也許能發現點有用的程式碼。

果不其然,在介紹頁面中看到了想要的內容,執行緒池的指標資料,只不過是拼接成了字串顯示而已。

"threadpool": {
      "source": "management.health.dubbo.status.extras",
      "status": {
        "level": "OK",
        "message": "Pool status:OK, max:200, core:200, largest:0, active:0, task:0, service port: 12345",
        "description": null
      }
}

然後就去翻dubbo-spring-boot-actuator的程式碼了,沒找到執行緒池這塊的程式碼。後面在dubbo.jar中找到了ThreadPoolStatusChecker這個類,核心邏輯在這裡面。現在已經解決了第一個問題,就是獲取到Dubbo的執行緒池物件。

參考micrometer中指標的實現,依葫蘆畫瓢?

執行緒池物件能拿到了,各種資料也就能獲取了。接下來的問題就是如何暴露出去給prometheus採集。

兩種方式,一種是自定義一個新的端點暴露,一種是直接在已有的prometheus端點中增加指標資料的輸出,也就是依葫蘆畫瓢。

看原始碼中已經有很多Metrics的實現了,我們也實現一個Dubbo 執行緒池的Metrics即可。

上圖框起來的就是一個已經存在的執行緒池Metrics,可以直接複用程式碼。

實現的主要邏輯就是實現一個MeterBinder介面,然後將你需要的指標進行輸出即可。於是打算在bindTo方法中獲取Dubbo的執行緒池物件,然後輸出指標。經過測試,在MeterBinder例項化的時候Dubbo還沒初始化好,拿不到執行緒池物件,繫結後無法成功輸出指標。

後面還是打算採用定時取樣的方式來輸出,自定義一個後臺執行緒,定時去輸出資料。可以用Timer,我這圖簡單就直接while迴圈了。

/**
 * Dubbo執行緒池指標
 *
 * @author yinjihuan
 */
@Configuration
public class DubboThreadMetrics {
    @Autowired
    private MeterRegistry meterRegistry;
    private final Iterable<Tag> TAG = Collections.singletonList(Tag.of("thread.pool.name", "dubboThreadPool"));
    @PostConstruct
    public void init() {
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
                Map<String, Object> executors = dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY);
                for (Map.Entry<String, Object> entry : executors.entrySet()) {
                    ExecutorService executor = (ExecutorService) entry.getValue();
                    if (executor instanceof ThreadPoolExecutor) {
                        ThreadPoolExecutor tp = (ThreadPoolExecutor) executor;
                        Gauge.builder("dubbo.thread.pool.core.size", tp, ThreadPoolExecutor::getCorePoolSize)
                                .description("核心執行緒數")
                                .baseUnit("threads")
                                .register(meterRegistry);
                        Gauge.builder("dubbo.thread.pool.largest.size", tp, ThreadPoolExecutor::getLargestPoolSize)
                                .description("歷史最高執行緒數")
                                .baseUnit("threads")
                                .register(meterRegistry);
                        Gauge.builder("dubbo.thread.pool.max.size", tp, ThreadPoolExecutor::getMaximumPoolSize)
                                .description("最大執行緒數")
                                .baseUnit("threads")
                                .register(meterRegistry);
                        Gauge.builder("dubbo.thread.pool.active.size", tp, ThreadPoolExecutor::getActiveCount)
                                .description("活躍執行緒數")
                                .baseUnit("threads")
                                .register(meterRegistry);
                        Gauge.builder("dubbo.thread.pool.thread.count", tp, ThreadPoolExecutor::getPoolSize)
                                .description("當前執行緒數")
                                .baseUnit("threads")
                                .register(meterRegistry);
                        Gauge.builder("dubbo.thread.pool.queue.size", tp, e -> e.getQueue().size())
                                .description("佇列大小")
                                .baseUnit("threads")
                                .register(meterRegistry);
                        Gauge.builder("dubbo.thread.pool.taskCount", tp, ThreadPoolExecutor::getTaskCount)
                                .description("任務總量")
                                .baseUnit("threads")
                                .register(meterRegistry);
                        Gauge.builder("dubbo.thread.pool.completedTaskCount", tp, ThreadPoolExecutor::getCompletedTaskCount)
                                .description("已完成的任務量")
                                .baseUnit("threads")
                                .register(meterRegistry);
                    }
                }
            }
        }).start();
    }
}

指標資訊:

配置執行緒池圖表

建立一個新的 dashboard 配置圖表,然後新建panel配置指標資訊

左側配指標資訊,右側選擇對應的圖表格式。需要注意的是,如果有多個服務例項,Metrics這邊最好是根據服務例項來顯示,需要在指標後面增加條件,如下:

dubbo_thread_pool_max_size_theads{application="$application", instance=~"$instance"}

關於作者:尹吉歡,簡單的技術愛好者,《Spring Cloud微服務-全棧技術與案例解析》, 《Spring Cloud微服務 入門 實戰與進階》作者, 公眾號猿天地發起人。

相關文章