Dubbo线程池问题思考Thread pool is EXHAUSTED!

问题

前几天,我们的生产上突然出现了这样一个问题,调下面的查询方法报错,线程池满的问题,如下图:
Dubbo线程池问题思考Thread pool is EXHAUSTED!_第1张图片

问题思路

简单思考:我们都知道线程池的参数都包含什么含义!核心线程数,可建线程数,存储任务队列,拒绝策略!这块,大家不熟悉或者忘记的可以再补习补习!了解这几个参数我们不禁会问,任务处理不了,不是还有队列存储么?存储不了不是还有拒绝策略么?再者,怎么两百个线程就同时都被占用了?理论我们的场景没有这种量啊!带着这些问题,我们从源码中得到一些答案!

根据上面的问题,我找到了Dubbo相关源码,看一下生产版本2.6.5!我目前测试的版本是2.7.6,对比两个版本线程池这块的逻辑应该是差不多的,粘贴下源码:
类的位置:
Dubbo线程池问题思考Thread pool is EXHAUSTED!_第2张图片
源码如下:

public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {

    protected static final Logger logger = LoggerFactory.getLogger(AbortPolicyWithReport.class);

    private final String threadName;

    private final URL url;

    private static volatile long lastPrintTime = 0;

    private static Semaphore guard = new Semaphore(1);

    public AbortPolicyWithReport(String threadName, URL url) {
        this.threadName = threadName;
        this.url = url;
    }

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        String msg = String.format("Thread pool is EXHAUSTED!" +
                        " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: %d)," +
                        " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",
                threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), e.getLargestPoolSize(),
                e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(),
                url.getProtocol(), url.getIp(), url.getPort());
        logger.warn(msg);
        dumpJStack();
        throw new RejectedExecutionException(msg);
    }

    private void dumpJStack() {
        long now = System.currentTimeMillis();

        //dump every 10 minutes
        if (now - lastPrintTime < 10 * 60 * 1000) {
            return;
        }

        if (!guard.tryAcquire()) {
            return;
        }

        Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                String dumpPath = url.getParameter(Constants.DUMP_DIRECTORY, System.getProperty("user.home"));

                SimpleDateFormat sdf;

                String OS = System.getProperty("os.name").toLowerCase();

                // window system don't support ":" in file name
                if(OS.contains("win")){
                    sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
                }else {
                    sdf = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss");
                }

                String dateStr = sdf.format(new Date());
                FileOutputStream jstackStream = null;
                try {
                    jstackStream = new FileOutputStream(new File(dumpPath, "Dubbo_JStack.log" + "." + dateStr));
                    JVMUtil.jstack(jstackStream);
                } catch (Throwable t) {
                    logger.error("dump jstack error", t);
                } finally {
                    guard.release();
                    if (jstackStream != null) {
                        try {
                            jstackStream.flush();
                            jstackStream.close();
                        } catch (IOException e) {
                        }
                    }
                }

                lastPrintTime = System.currentTimeMillis();
            }
        });

    }

}

根据上述源码,我们知道Dubbo定制了拒绝策略!从方法看,他是会把栈导出到系统文件的!Dubbo默认线程池核心线程数是200个的,可以通过参数设置!
Dubbo线程池问题思考Thread pool is EXHAUSTED!_第3张图片

复现问题

本地服务就可以制造个现场如下:
代码位置:码云链接
提供者代码:

@Service
public class TestApiImpl implements TestApi {
    private static Object lock = new Object();

    @Override
    public void test(Test test) {
        System.out.println(Thread.currentThread().getName() + " 进入争抢资源...");
        synchronized (lock) {
            try {
                Thread.sleep(50000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("lock....." + Thread.currentThread().getName());
        }
    }
}

这里说一个小插曲:sleep是不会干预锁行为的!这点不同于wait方法!这个地方想要阻塞其他线程就要长期持有锁!

消费者代码:

@RestController
public class UserController {

    @Reference(timeout = 10000)
    private TestApi testApi;
    
    @GetMapping("/test")
    public void test(){
        Test test = new Test();
        testApi.test(test);
    }
}

启动服务之后,用jmeter测试一下:
Dubbo线程池问题思考Thread pool is EXHAUSTED!_第4张图片
开大于200个线程去调用/test的接口,就会报错:
Dubbo线程池问题思考Thread pool is EXHAUSTED!_第5张图片

有上面的源码可知,当出现这种情况的时候,会在系统用户的当前目录下生成出Dubbo_JStack.log.2020-05-22_11-55-23类似于这样的线程栈文件!我们可以通过这些信息查看都是为啥线程发生了阻塞等状况!
Dubbo线程池问题思考Thread pool is EXHAUSTED!_第6张图片

当然我们的例子我们是知道的,是因为对象锁被长期占用不释放导致的!
Dubbo线程池问题思考Thread pool is EXHAUSTED!_第7张图片

这里只是起到一个抛砖引玉的作用,提供大家思路去解决这样的问题!但是具体需要看一下,你们的问题到底是出在哪里?!是使用redis 无法初始化链接,连接数全部被占用!还是队列等问题,那就具体问题具体分析了,祝好!

你可能感兴趣的:(DUBBO,Dubbo,Dubbo线程池,Thread,pool,is,EXHAUSTED)