我去dubbo线程池又被打爆(打满)了java.util.concurrent.RejectedExecutionException: Thread pool is EXHAUSTED

本文详细讲述了Dubbo服务中线程池满的问题排查过程,包括线程dump文件的收集、分析工具SmartJavathreadDumper的应用,以及通过AOP解决日志性能瓶颈。还探讨了logback异步打印引发的阻塞,并提供了log4j2升级方案。

最近线上系统经常告警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

牺牲了午休时间,用心总结,觉得有用的话,收藏转发,哈哈!

Dubbo RPC 调用过程中,出现 `UNKNOWN status` 异常通常表示在服务调用链路中的某个环节发生了不可预期的错误,导致调用状态无法明确归类为成功或失败。这类问题可能源于网络通信异常、服务端处理逻辑错误、线程池资源耗尽、序列化失败等多方面原因。 当出现 `RpcException: UNKNOWN status` 时,调用方无法获得明确的响应结果,且可能长时间处于等待状态,直到超时[^2]。此类异常常见于以下几种情况: ### 1. **序列化失败** 当服务提供方返回的对象未实现 `java.io.Serializable` 接口,或类结构在消费者端不可见时,会导致反序列化失败,从而触发 UNKNOWN 状态。例如,引用中提到的 `com.insis.domain.User` 类未实现 `Serializable` 接口就属于此类问题[^1]。 ### 2. **线程池资源耗尽** Dubbo 默认使用固定大小的线程池来处理请求。当并发请求量超过线程池容量,且等待队列长度为 0 时,新请求将被直接丢弃,导致调用方得不到响应,出现 UNKNOWN 状态异常。此时可通过修改 `dispatcher` 策略为 `message`,并适当调整线程池参数来缓解问题[^2]。 示例配置如下: ```xml <dubbo:protocol name="dubbo" port="8888" threads="500" dispatcher="message" /> ``` ### 3. **服务端异常未被捕获** 如果服务提供方在执行远程方法时抛出未捕获的异常,且未正确封装为 `RpcException`,也可能导致调用方接收到 UNKNOWN 状态的响应。 ### 4. **网络中断或超时** 在 RPC 调用过程中,若网络连接中断或响应超时,调用方将无法确认请求是否成功完成,也可能返回 UNKNOWN 状态。可通过配置合理的超时时间与重试策略来缓解此类问题。 示例配置重试机制: ```xml <dubbo:reference id="userService" interface="com.insis.service.UserService" check="false" timeout="5000" retries="2"/> ``` ### 5. **服务端崩溃或无响应** 当服务端因 JVM 崩溃、死锁或资源耗尽等原因无法响应请求时,调用方可能长时间得不到响应,最终触发 UNKNOWN 状态异常。此时可通过获取线程堆栈日志(如使用 `jstack`)进行分析[^3]。 示例获取 jstack 日志: ```bash jstack 99668 > card-service_down.log ``` ### 6. **协议不兼容** 当消费者与提供者使用的 Dubbo 协议版本不一致,或数据格式不兼容时,也可能导致 UNKNOWN 状态的出现。 ### 解决建议 - 确保所有被传输的对象实现 `Serializable` 接口,且类路径一致。 - 合理配置线程池大小与 `dispatcher` 策略,避免请求被丢弃。 - 设置合理的超时时间和重试机制,增强系统的容错能力。 - 使用日志与线程堆栈分析工具定位服务端问题。 - 升级 Dubbo 到最新稳定版本,以获得更好的异常处理机制与兼容性支持。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值