前言
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微服務 入門 實戰與進階》作者, 公眾號猿天地發起人。