线程池满Thread pool exhausted排查和解决方案

目录
  • 发生{Thread pool is EXHAUSTED}时的服务器日志
  • 产生原因
    • 大并发导致配置的线程不够用
    • 调用外部接口程序出现阻塞,等待时间过长
  • 定位方式
  • Dubbo线上Thread pool is EXHAUSTED问题跟踪

发生{Thread pool is EXHAUSTED}时的服务器日志

产生原因

大并发导致配置的线程不够用

在初始时候,dubbo协议配置,我是使用dubbo默认的参数,dubbo线程池默认是固定长度线程池,大小为200。

一开始出现线程池满的问题,本以为是并发量大导致的,没做太多关注,运维也没有把相应的日志dump下来,直接重启了。

所以一开始只是优化了dubbo的配置。调大固定线程池数量为400,并且将dispatcher转发由默认的配置"all"改为message。

all表示所有消息都派发到线程池,包括请求,连接事件,断开事件,心跳等。message表示只有请求响应消息派发到线程池,其他连接断开事件,心跳等消息,直接在IO线程上执行。

同时开启了访问日志记录,观察是不是有出现其他消费系统有短时间大并发调用接口的情况。

accesslog设为true,将向logger中输出访问日志,也可填写访问日志文件路径,直接把访问日志输出到指定文件 

<dubbo:protocol name="dubbo" port="33112" accesslog="true" dispatcher="message" threads="500"/><!--开启访问日志记录 -->

调用外部接口程序出现阻塞,等待时间过长

通过日志观察几天下来,大概每天接口的调用量在60~70万左右,瞬时并发调用量也就十几,理论上不应该出现线程池满的情况,所以这时候就怀疑是不是有出现线程Blocked的情况,这时候便想通过日志记录一下线上的dubbo线程池的情况,即在未出现线程池满之前能够及时发现问题。

所以就增加了一个切面。切点是接口中的所有方法。在调用前和调用后打印线程池信息的日志。

通过查看线程池源码我们可知,线程池的toString()方法,记录了线程池的情况信息

定位方式

通过运用统计发生异常时间段内的接口TPS/QPS,来定位是否大并发导致的线程配置不够用

正常来说需要在程序在调外部接口时需要打印异常日志,可以通过查询log文件的方式,定位是否是Blocked导致的

通过切面类日志打印日志来定位问题,具体如下:

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.common.store.DataStore;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @ClassName  DubboAOP
 * @Description
 * @Author  libo
 * @Date  2019/7/25 11:46
 * @Version  1.0
 **/
@Component
@Aspect
public class DubboAOP {

    private static final Logger logger = LoggerFactory.getLogger(DubboAOP.class);

    @Pointcut("execution(* com.ncarzone.oa.biz.facade.EmployeeServiceFacadeImpl.*(..))")
    public void pointCut(){

    }

    @Before("pointCut()")
    public void before(){
        logger.info("======before()======"+Thread.currentThread().getName());
        printDubboThreadInfo();

    }

    @After("pointCut()")
    public void after(){
        printDubboThreadInfo();
        logger.info("======after()==="+Thread.currentThread().getName());
    }

    private void printDubboThreadInfo(){
        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;
                logger.info("===dubboThread======"+tp.toString());
            }
        }
    }
}

可以根据我们写的切面的日志打印信息可以看到活跃线程一直在增加,即一个新的请求过来之后,就没有下文了,线程没有运行完,自然就无法被回收到线程池中。因而判断极有可能是线程出现阻塞或者是一直在等待的情况。所以这次直接让运维人员帮忙dump下线程日志。 jstack + pid  xxx.log

通过线程dump日志,我们可以看到出现了大量线程的等待,dump中记录了出问题的代码之处。通过分析,可知在获取redis连接,去取redis数据的时候,由于没有拿到redis的连接,即getResource方法执行卡住了,同时项目的redis配置又没有设置获取连接的最大超时时间,通过redis源码我们可知,如果没有设置,则默认是-1,即可能会出现长时间等待获取连接的情况。它会从空闲的连接队列(private final LinkedBlockingDeque<PooledObject<T>> idleObjects)中取第一个,因为用的是takefirst()方法,即如果没有空闲连接,则会一直等待。而pollFirst(),超时则会返回成功或失败

因而我们需要在项目的jedis连接池配置中增加MaxWaitMillis配置,我这里设置的是500毫秒

现在知道了是此种情况导致的,但是因为我项目里配置的最大分配连接是600,而项目里使用redis的地方并不多,理论上不应该出现redis连接池满的情况,应该还是有其他问题。所以继续看了thread dump日志,发现了问题点所在,因为之前和钉钉的开放平台对接,做了一个回调接口,但偶尔会出现重复回调的情况,所以就做了一个redis锁,来避免这个问题。但是这边代码有严重的问题,如果jedis设置缓存不成功,则会进入线程休眠(Thread.sleep(1000)),线程休眠是不会释放所持有的连接的,而这个地方就陷入了死循环。导致该连接被一直占用,从而连接池中可用的连接越来越少,直到被占满

将此处代码做了修改.使用sleep虽然保证了线程安全,但是影响性能。修改为根据具有唯一性的字段进行加锁

Dubbo线上Thread pool is EXHAUSTED问题跟踪

今天发现了服务在某段时间内大量出现这个异常:

detail msg:Thread pool is EXHAUSTED!

下游服务的 Dubbo 线程池满了,经过沟通得知下游服务在那个时间段之内出现了慢 SQL,导致数据库连接被打满,进而影响了其他 Dubbo 服务的。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Java并发包中线程池ThreadPoolExecutor

    一.线程池简介 线程池的使用主要是解决两个问题:①当执行大量异步任务的时候线程池能够提供更好的性能,在不使用线程池时候,每当需要执行异步任务的时候直接new一个线程来运行的话,线程的创建和销毁都是需要开销的.而线程池中的线程是可复用的,不需要每次执行异步任务的时候重新创建和销毁线程:②线程池提供一种资源限制和管理的手段,比如可以限制线程的个数,动态的新增线程等等. 在下面的分析中,我们可以看到,线程池使用一个Integer的原子类型变量来记录线程池状态和线程池中的线程数量,通过线程池状态来控制任

  • Java并发编程面试之线程池

    目录 什么是线程池 线程池好处 线程池的执行流程 怎么用线程池 corePoolSize maximumPoolSize keepAliveTime unit workQueue threadFactory ejectedExecutionHandler 线程池参数如何设置? 监控线程池 总结 什么是线程池 是一种基于池化思想管理线程的工具.池化技术:池化技术简单点来说,就是提前保存大量的资源,以备不时之需.比如我们的对象池,数据库连接池等. 线程池好处 我们为什么要使用线程池,直接new th

  • java线程池ThreadPoolExecutor的八种拒绝策略示例详解

    目录 池化设计思想 线程池触发拒绝策略的时机 JDK内置4种线程池拒绝策略 拒绝策略接口定义 AbortPolicy(中止策略) DiscardPolicy(丢弃策略) DiscardOldestPolicy(弃老策略) 第三方实现的拒绝策略 Dubbo 中的线程拒绝策略 Netty 中的线程池拒绝策略 ActiveMQ 中的线程池拒绝策略 PinPoint 中的线程池拒绝策略 谈到 Java 的线程池最熟悉的莫过于 ExecutorService 接口了,jdk1.5 新增的 java.uti

  • 线程池满Thread pool exhausted排查和解决方案

    目录 发生{Thread pool is EXHAUSTED}时的服务器日志 产生原因 大并发导致配置的线程不够用 调用外部接口程序出现阻塞,等待时间过长 定位方式 Dubbo线上Thread pool is EXHAUSTED问题跟踪 发生{Thread pool is EXHAUSTED}时的服务器日志 产生原因 大并发导致配置的线程不够用 在初始时候,dubbo协议配置,我是使用dubbo默认的参数,dubbo线程池默认是固定长度线程池,大小为200. 一开始出现线程池满的问题,本以为是并

  • 基于springcloud异步线程池、高并发请求feign的解决方案

    ScenTaskTestApplication.java package com.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; /** * @author scen *

  • java线程池工作队列饱和策略代码示例

    线程池(Thread Pool) 是并行执行任务收集的实用工具.随着 CPU 引入适合于应用程序并行化的多核体系结构,线程池的作用正日益显现.通过 ThreadPoolExecutor类及其他辅助类,Java 5 引入了这一框架,作为新的并发支持部分. ThreadPoolExecutor框架灵活且功能强大,它支持特定于用户的配置并提供了相关的挂钩(hook)和饱和策略来处理满队列 Java线程池会将提交的任务先置于工作队列中,在从工作队列中获取(SynchronousQueue直接由生产者提交

  • Python探索之自定义实现线程池

    为什么需要线程池呢? 设想一下,如果我们使用有任务就开启一个子线程处理,处理完成后,销毁子线程或等得子线程自然死亡,那么如果我们的任务所需时间比较短,但是任务数量比较多,那么更多的时间是花在线程的创建和结束上面,效率肯定就低了.     线程池的原理: 既然是线程池(Thread pool),其实名字很形象,就是把指定数量的可用子线程放进一个"池里",有任务时取出一个线程执行,任务执行完后,并不立即销毁线程,而是放进线程池中,等待接收下一个任务.这样内存和cpu的开销也比较小,并且我们

  • 一种类似JAVA线程池的C++线程池实现方法

    什么是线程池 线程池(thread pool)是一种线程使用模式.线程过多或者频繁创建和销毁线程会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着管理器分配可并发执行的任务.这避免了在处理短时间任务时创建与销毁线程的代价,以及保证了线程的可复用性.线程池不仅能够保证内核的充分利用,还能防止过分调度. 线程池的实现 线程池在JAVA平台上已经有成熟的实现方式,本文介绍参考JAVA线程池实现方式实现的C++线程池类库. 该类库代码已上传至github仓库中,下载地址为:ht

  • Java 使用线程池执行多个任务的示例

    在执行一系列带有IO操作(例如下载文件),且互不相关的异步任务时,采用多线程可以很极大的提高运行效率.线程池包含了一系列的线程,并且可以管理这些线程.例如:创建线程,销毁线程等.本文将介绍如何使用Java中的线程池执行任务. 1 任务类型 在使用线程池执行任务之前,我们弄清楚什么任务可以被线程池调用.按照任务是否有返回值可以将任务分为两种,分别是实现Runnable的任务类(无参数无返回值)和实现Callable接口的任务类(无参数有返回值).在打代码时根据需求选择对应的任务类型. 1.1 实现

  • C++线程池实现代码

    前言 这段时间看了<C++并发编程实战>的基础内容,想着利用最近学的知识自己实现一个简单的线程池. 什么是线程池 线程池(thread pool)是一种线程使用模式.线程过多或者频繁创建和销毁线程会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着管理器分配可并发执行的任务.这避免了在处理短时间任务时创建与销毁线程的代价,以及保证了线程的可复用性.线程池不仅能够保证内核的充分利用,还能防止过分调度. 思路 个人对线程池的理解是:利用已经创建的固定数量的线程去执行指定的任

  • Java线程池ThreadPoolExecutor原理及使用实例

    引导 要求:线程资源必须通过线程池提供,不允许在应用自行显式创建线程: 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题.如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗内存或者"过度切换"的问题. 线程池介绍线程池概述   线程池,顾名思义是一个放着线程的池子,这个池子的线程主要是用来执行任务的.当用户提交任务时,线程池会创建线程去执行任务,若任务超过了核心线程数的时候,会在一个任务队列里进行排队等待,这个详细流程,我们会后面细

  • 一文彻底搞懂java多线程和线程池

    目录 什么是线程 一. Java实现线程的三种方式 1.1.继承Thread类 1.2.实现Runnable接口,并覆写run方法 二. Callable接口 2.1 Callable接口 2.2 Future接口 2.3 Future实现类是FutureTask. 三. Java线程池 3.1.背景 3.2.作用 3.3.应用范围 四. Java 线程池框架Executor 4.1.类图: 4.2 核心类ThreadPoolExecutor: 4.3 ThreadPoolExecutor逻辑结

  • 客户端JavaScript的线程池设计详解

    目录 1.介绍: 2.准备工作: 3.测试spark-md5是否正常工作: 4.线程池设计 5.spark-md5对文件进行md5编码 6.大量文件进行MD5加密并使用线程池优化 总结 1.介绍: 本打算在客户端JavaScript进行机器学习算法计算时应用线程池来优化,就像()演示的神经网络.但是由于各种原因不了了之了.本次遇到了一个新的问题,客户端的MD5运算也是耗时操作,如果同时对多个字符串或文件进行MD5加密就可以使用线程池来优化. 2.准备工作: 到npm官网搜索spark-md5,到

随机推荐