優化排查線程阻塞:CompletableFuture 和 DiscardPolicy
本文轉載自微信公眾號「潛行前行」,作者cscw 。轉載本文請聯系潛行前行公眾號。
問題發現
1 前天大佬通過prometheus發現 tomcat http busy狀態的線程這幾天呈線性遞增。每一天增加3個
排查問題
1:找到busy線程在哪。通過jvm自帶的 jps 命令可以找到服務對應的進程ID:66182$>$top -Hp 66182
$pidstat -u -p 66182 1 5
大部分的線程都正常,cpu利用率不高,而且線程ID變動快,基本排除 死循環、CPU 空轉的問題
2:既然不是死循環、CPU空轉。那是不是代碼阻塞的問題呢。導出 66182 進程jvm的 stack 文件 jstack -l 66182 > block66182.jstack。因為知道是http 線程的問題。http 的開頭一般都是 http-nio ??梢允褂?grep -A 15 'http-nio' block66182.jstack 輸出一些關鍵信息。找了很久,多數http 都是正常狀態。然后還找到了項目代碼 updateXYDVerifiedCodeByDate 之類的。定位到具體,發現是一個定時任務
3 查了下 xxl-task 調度日志。凌晨左右調度了十次,失敗了三次,和在prometheus發現的 busy http線程增加的規律是一致的
4 查找代碼發現是 CompletableFuture 調用get阻塞住了。后來改成 future.get(15, TimeUnit.SECONDS);。則每隔 15 秒報錯一次。但是在promethues 監控到 verifiedCodeQueryExecutor 的線程隊列是空的。
4.1 promethues 監控到的線程隊列數為空
5 沒有任務 CompletableFuture 的get方法還在執行,查看下 verifiedCodeQueryExecutor 的定義。發現,阻塞隊列的拒絕策略 是 DiscardPolicy 丟棄。也就是任務丟棄了不被執行,而封裝成的CompletableFuture 自然就不會有結果返回,因此一直會被阻塞,而改了代碼則是超時返回。真相大白。。。。。
解決問題
1:隊列無限,好像不太好,不接受
2:自定義拒絕策略
- new RejectedExecutionHandler(){
- @Override
- public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
- throw new RuntimeException(" over size error ");
- }
- }
3:但考慮到任務必須被處理掉,任務不能被丟棄啊。so 暫時用 CallerRunsPolicy 策略,executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
4:后面再優化