dubbo線程池又被打爆(打滿)了java.util.concurrent.RejectedExecutionException: Thread pool is EXHAUSTED

轉載:https://blog.csdn.net/kevin_mails/article/details/121764780?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-121764780-blog-124236206.235%5Ev27%5Epc_relevant_recovery_v2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-121764780-blog-124236206.235%5Ev27%5Epc_relevant_recovery_v2&utm_relevant_index=2

最近線上系統經常告警dubbo線程池打滿報錯如下:

172.28.152.53/Caused by: java.util.concurrent.RejectedExecutionException: Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-172.28.149.131:20880, Pool Size: 500 (active: 500, core: 500, max: 500, largest: 500), Task: 71795841 (completed: 71795342), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://172.28.149.131:20880!

對這個告警做了協查,記錄一下,給大家一個參考。

dubbo線程池打滿後,會立即生成一個線程dump文件,這裏先去服用器上拿文件,立即生成一個線程dump文件需要配置一下啓動參數

-Ddubbo.application.dump.directory=/data/dataLogs/jetty/dump
以後發生 dubbo線程池打滿就到以到/data/dataLogs/jetty/dump下找文件,另外大家也可以通過jstack jmap等命令分析,我們因爲投產使用不了,這裏大家自行學習。

拿到dump文件後,打開看看

足足有2W多行,這麼一行行看也沒問題,這裏推薦個分析工具:Smart Java thread dump analyzer - thread dump analysis in seconds

上傳文件後,生產一個分析結果頁面,很好用,如下圖。

 

上圖BLOCKED 的線程有2個,點進去看一下明細

 

綜合業務和線程棧分析: 我們做了一個日誌打印的aop,在方法入口切入,添加一個uuid生成的TraceID,通過MDC將日誌串起來,我們使用java的UUID對象的randomUUID()生成id,randomUUID底層使用SecureRandom,像註釋裏講了,多線程併發時,會BLOCK,所以性能不是很高。

 

解決辦法:

我自己寫一個方法:

package com.test.java8;

import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;

public class MyUUID {

// static final AtomicLong counter = new AtomicLong(System.nanoTime());
static final AtomicLong counter = new AtomicLong(System.currentTimeMillis());

public static String getUUID() {
return String.valueOf(counter.getAndIncrement());
}


public static String getUuid() {
return UUID.randomUUID().toString().replace("-", "");
}


public static void main(String[] args) {
int rCount = 100000000;
System.out.println("開始隨機數生成測試!");
Long begin = System.currentTimeMillis();
for (int i = 0; i < rCount; i++) {
MyUUID.getUUID();
// System.out.println(MyUUID.getUUID());
}
System.out.printf("完成隨機數生成,用時" + (System.currentTimeMillis() - begin) + "ms");

System.out.println("-----------------------------------------------------------------");

// Long begin2 = System.currentTimeMillis();
// for (int i = 0; i < rCount; i++) {
// MyUUID.getUuid();
// }
// System.out.printf("完成隨機數生成,用時" + (System.currentTimeMillis() - begin2) + "ms");
// System.out.println(MyUUID.getUUID());
// System.out.println(MyUUID.getUuid());
}
}
注意!:不保證集羣唯一的,這裏因爲主要是Trace 所以問題不大。如果有集羣唯一的需求不要用我這個方法,可以參考SnowFlake等算法。

至此這個dump就分析完了。

那邊在這次協查中,還有一個dump鎖在了另外一個地方,貼一下圖:

 

 

通過分析:

項目中使用的是logback的實現,項目配置的是AsyncAppender異步打印,異步打印日誌會被存放在一個SynchronousQueue,然後再由消費線程來消費打印,以提升系統性能(建議大家生產一定要用異步),SynchronousQueue是阻塞隊列,由於請求量大,打印的日誌又多,最終導致消費速度跟不上,後面的添加隊列都阻塞了,業務線程都夯在這裏。

解決方案:

1.刪除一些多餘,無用日誌(打印日誌確實也很耗性能),打印儘量精簡(我們通過此方法解決)。

2.升級日誌框架(考慮中),目前log4j2看起來是不錯的選擇,官方也給了一些性能比較數據Log4j – Performance,log4j2的異步消息存儲不是阻塞隊列,是通過Disruptor高性能隊列實現性能更強勁。更有美團在2016年將使用log4j2作爲內部規範(高性能隊列——Disruptor - 美團技術團隊),哈哈,看來我們的步子還是有點慢了!

也做了一個集成log4j2的demo,僅供參考:GitHub - kevinmails/h2-test
————————————————
版權聲明:本文爲CSDN博主「kevin_mails」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/kevin_mails/article/details/121764780

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章