dubbo线程池为什么耗尽

文章概述

大家可能都遇到过DUBBO线程池打满这个问题,报错如下,本文我们就一起分析DUBBO线程池打满这个问题。
cause: org.apache.dubbo.remoting.RemotingException: Server side(10.0.0.100,20881) thread pool is exhausted, detail msg:Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-10.0.0.100:20881, Pool Size: 800 (active: 800, core: 800, max: 800, largest: 800), Task: 50397601 (completed: 50396801), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://10.0.0.100:20881!

1 DUBBO线程模型

先看一张图大概了解
未命名文件 (5).png

** IO线程**

IO线程的工作实际上就是处理字节流的输入输出,对消息的读取,序列化,不涉及业务操作
NettyServer中启动netty服务端,初始化boss和work线程信息

  protected void doOpen() throws Throwable {bootstrap = new ServerBootstrap();bossGroup = NettyEventLoopFactory.eventLoopGroup(1, "NettyServerBoss");workerGroup = NettyEventLoopFactory.eventLoopGroup(getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),"NettyServerWorker");final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);channels = nettyServerHandler.getChannels();bootstrap.group(bossGroup, workerGroup).channel(NettyEventLoopFactory.serverSocketChannelClass()).option(ChannelOption.SO_REUSEADDR, Boolean.TRUE).childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE).childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// FIXME: should we use getTimeout()?int idleTimeout = UrlUtils.getIdleTimeout(getUrl());NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);if (getUrl().getParameter(SSL_ENABLED_KEY, false)) {ch.pipeline().addLast("negotiation",SslHandlerInitializer.sslServerHandler(getUrl(), nettyServerHandler));}ch.pipeline().addLast("decoder", adapter.getDecoder()).addLast("encoder", adapter.getEncoder()).addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS)).addLast("handler", nettyServerHandler);}});// bindChannelFuture channelFuture = bootstrap.bind(getBindAddress());channelFuture.syncUninterruptibly();channel = channelFuture.channel();}

这里分别看线程数量

    bossGroup = NettyEventLoopFactory.eventLoopGroup(1, "NettyServerBoss");workerGroup = NettyEventLoopFactory.eventLoopGroup(getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),"NettyServerWorker");
int DEFAULT_IO_THREADS = Math.min(Runtime.getRuntime().availableProcessors() + 1, 32);

boss线程设置为1
主要看work线程(IO线程)
从url中获取线程数,如果没设置的话,设置当前机器的线程数,最少设置为32个
这个配置是iothreads,如果配置的这样配置。但是线程池耗尽并不是io线程数量不够的原因

provider:iothreads: 100
@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);handler.received(channel, msg);}
  @Overridepublic void received(Channel channel, Object message) throws RemotingException {setReadTimestamp(channel);if (isHeartbeatRequest(message)) {Request req = (Request) message;if (req.isTwoWay()) {Response res = new Response(req.getId(), req.getVersion());res.setEvent(HEARTBEAT_EVENT);channel.send(res);if (logger.isInfoEnabled()) {int heartbeat = channel.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);if (logger.isDebugEnabled()) {logger.debug("Received heartbeat from remote channel " + channel.getRemoteAddress()+ ", cause: The channel has no data-transmission exceeds a heartbeat period"+ (heartbeat > 0 ? ": " + heartbeat + "ms" : ""));}}}return;}if (isHeartbeatResponse(message)) {if (logger.isDebugEnabled()) {logger.debug("Receive heartbeat response in thread " + Thread.currentThread().getName());}return;}handler.received(channel, message);}

消息的不同类型有不同的处理方式如果是心跳直接就发送回去了,
如果是业务请求那么交给业务线程池处理

  @Overridepublic void received(Channel channel, Object message) throws RemotingException {ExecutorService executor = getPreferredExecutorService(message);try {executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));} catch (Throwable t) {if(message instanceof Request && t instanceof RejectedExecutionException){sendFeedback(channel, (Request) message, t);return;}throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);}}

业务线程池

初始化
不同线程池策略会创建不同特性的线程池:
dubbo提供了不同的线程池类型

fixed
包含固定个数线程cached
线程空闲一分钟会被回收,当新请求到来时会创建新线程limited
线程个数随着任务增加而增加,但不会超过最大阈值。空闲线程不会被回收eager
当所有核心线程数都处于忙碌状态时,优先创建新线程执行任务,而不是立即放入队列

一般实际使用的就是fixed

public class FixedThreadPool implements ThreadPool {@Overridepublic Executor getExecutor(URL url) {String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,queues == 0 ? new SynchronousQueue<Runnable>() :(queues < 0 ? new LinkedBlockingQueue<Runnable>(): new LinkedBlockingQueue<Runnable>(queues)),new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));}}

这里主要看两个参数,分别是线程数,和队列长度。默认的线程数是200,queue默认使用SynchronousQueue
SynchronousQueue由于其独有的线程一一配对通信机制,由于内部没有使用AQS,而是直接使用CAS,其并没有存储任务的队列就是将任务与线程进行匹配,如果任务进来,没用可用线程,那么将直接拒绝,这也是我们碰到拒绝策略的原因
如果需要配置

dubbo:protocol:threads: 800queues: 10000

业务线程线程池拒绝

这里就可以看到线程池拒绝AbortPolicyWithReport

 @Overridepublic 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);}

也就是开头的那个报错,这里在发生问题会自动dump stack信息

线程池中的 getTaskCount 和 getCompletedTaskCount 是两个重要的方法,它们用于获取线程池的任务和已完成任务的统计信息。

  1. getTaskCount: 这个方法返回线程池中的当前任务数。它包括正在执行的任务和等待执行的任务。换句话说,它返回的是线程池中所有任务的总数,包括那些尚未开始执行的任务。
  2. getCompletedTaskCount: 这个方法返回线程池已完成的任务数量。它只计算那些已经完成执行的任务,而不包括正在执行或等待执行的任务。

再回头我们的那个报错。
Pool Size: 800 (active: 800, core: 800, max: 800, largest: 800), Task: 50397601 (completed: 50396801)

2、估算合适的线程数,寻找慢业务

我们知道DUBBO会选择线程池策略进行业务处理,那么如何估算可能产生的线程数呢?我们首先分析一个问题:一个公司有7200名员工,每天上班打卡时间是早上8点到8点30分,每次打卡时间系统耗时5秒。请问RT、QPS、并发量分别是多少?
RT表示响应时间,问题已经告诉了我们答案:
RT = 5

QPS表示每秒查询量,假设签到行为平均分布:
QPS = 7200 / (30 * 60) = 4

并发量表示系统同时处理的请求数量:
并发量 = QPS x RT = 4 x 5 = 20

根据上述实例引出如下公式:
并发量 = QPS x RT

如果系统为每一个请求分配一个处理线程,那么并发量可以近似等于线程数。基于上述公式不难看出并发量受QPS和RT影响,这两个指标任意一个上升就会导致并发量上升。
但是这只是理想情况,因为并发量受限于系统能力而不可能持续上升,例如DUBBO线程池就对线程数做了限制,超出最大线程数限制则会执行拒绝策略,而拒绝策略会提示线程池已满,这就是DUBBO线程池打满问题的根源。下面我们分别分析RT上升和QPS上升这两个原因。
注意上面仅仅是一个例子,实际上一个服务远比例子复杂,实践往往需要不断的调参数。才能找到合理的值
线程池耗尽,往往是因为某个业务慢导致,我们应该寻找执行缓慢的堆栈,例如使用arthas来监控。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/295370.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数运算·字符串相加·阶乘

大数&#xff0c;就是C/C中利用基本类型所不能存储的数字&#xff0c;少则数十位&#xff0c;大则几万位&#xff0c;如何存储和计算大数就是本文的内容。 在C和C中&#xff0c;没有存储大数的数据结构&#xff0c;就算 unsigned long long也只能表示19位的数字  如果我们用d…

【JAVA面试题】什么是引用传递?什么是值传递?

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 前言 博客的正文部分可以详细介绍Java中参数传递的机制&#xff0c;强调Java是按值传递的&#xff0c;并解释了基本数据类型和对象引用在这种传…

代码随想录算法训练营第60天|84.柱状图中最大的矩形

JAVA代码编写 84. 柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,6,2,3] 输出&…

mysql复习笔记06(小滴课堂)

mysql数据安全之备份的背景意义 介绍数据备份 mysql数据安全之mysqldump备份实例&#xff08;跨机器&#xff09; 一般存在于mysql的bin目录下。中小型企业&#xff0c;数据量不是特别大的时候可以使用这个方式备份。 可以看到备份过来的库了。这是备份单个数据库。 如果想备份…

Kioptrix-3

靶场下载地址 https://download.vulnhub.com/kioptrix/KVM3.rar 信息收集 # Nmap 7.94 scan initiated Thu Dec 21 21:52:25 2023 as: nmap -sn -oN live.nmap 192.168.1.0/24 Nmap scan report for 192.168.1.1 (192.168.1.1) Host is up (0.00048s latency). MAC Address:…

慎投!新增2本期刊剔除!中科院2区TOP仍被标记长达6个月!(内附1区TOP仅21天录用)

期刊动态&#xff1a;2本「On Hold」期刊被剔除 美国时间2023年12月19日&#xff0c;科睿唯安更新了WOS期刊目录&#xff08;参考&#xff1a;警惕&#xff01;5本剔除&#xff01;中科院1区TOP&#xff0c;IF8.8&#xff0c;预警高风险期刊更名&#xff01;&#xff09;&…

运行游戏显示缺少d3dx9_42.dll怎么办,三步即可完美解决

在我们使用电脑玩游戏&#xff0c;工作的时候&#xff0c;偶尔会遇到一些错误提示&#xff0c;其中之一就是缺少d3dx9_42.dll。这个错误通常出现在运行某些游戏或应用程序时&#xff0c;它表示计算机缺少了DirectX 9组件中的d3dx9_42.dll文件。为了解决这个问题&#xff0c;下面…

Linux-Keepalived(VRRP协议)高可用集群搭建

Linux-Keepalived&#xff08;VRRP协议&#xff09;高可用集群搭建 一、VRRP简介1.1 什么是VRRP&#xff1f;1.2 keepalived是什么&#xff1f;1.3 keepalived工作原理 二、实操配置过程2.1 试验模型2.2. Keepalived监控和维护VRRP集群的步骤2.2.1 安装keepalived2.2.2 配置kee…

RocketMQ系统性学习-RocketMQ高级特性之消息存储在Broker的文件布局

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 【11来了】文章导读地址&#xff1a;点击查看文章导读&#xff01; &#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f3…

【go-zero】 go-zero API 如何接入 Nacos 被 java 服务调用 | go集成java服务

一、场景 外层使用的是springcloud alibaba 这一套java的分布式架构 然后需要接入go-zero的api服务 这里我们将对api服务接入Nacos进行一个说明 二、实战 1、package 因为使用的是go-zero框架 这里我们会优先使用go-zero生态的包 github 包如下: github.com/nacos-group/naco…

yarn : 无法将“yarn”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。‘yarn‘ 不是内部或外部命令,也不是可运行的程序.解决方案

文章目录 报错截图介绍方法一方法二评论截图 报错截图 介绍 我的npm已经安装好了, 是可以运行npm -v 来查看版本的 这个时候报 yarn 不是内部或外部命令 相信你的npm也已经安装好了 我下面两个方法都进行了, 具体起作用的我也不知道是哪个, 都试试吧, 我成功了 注意尝试后关…

python区块链简单模拟【01】

完整代码 https://gitee.com/ihan1001 https://github.com/ihan1001 重点&#xff1a;时间戳&#xff0c;MD5哈希&#xff0c;SHA256哈希&#xff0c;base64一种用64个字符表示任意二进制数据的方法&#xff0c;ECC椭圆曲线算法 import time time.time()datetime.now().strfti…