dubbo的dispatcher设置原理
在上回《Dubbo源代码实现六》中我们已经了解到,对于Dubbo集群中的Provider角色,有IO线程池(默认无界)和业务处理线程池(默认200)两个线程池,所以当业务的并发比较高,或者某些业务处理变慢,业务线程池就很容易被“打满”,抛出“RejectedExecutionException: Thread pool is EXHAUSTED! ”异常。当然,前提是我们每给Provider的线程池配置等待Queue。
既然Provider端已经抛出异常,表明自己已经受不了了,但线上我们却发现,Consumer无动于衷,发出的那笔请求还在那里傻傻的候着,直到超时。这样极其容易导致整个系统的“雪崩”,因为它违背了fail-fast原则。我们希望一旦Provider由于线程池被打满而无法收到请求,Consumer应该立即感知然后快速失败来释放线程。后来发现,完全是Dispatcher配置得不对,默认是all,我们应该配置成message。
我们从源码角度来看看到底是咋回事,这里先假设我们用的是Netty框架来实现IO操作,上回我们已经提到过,NettyHandler、NettyServer、MultiMessageHandler、HeartbeatHandler都实现了ChannelHandler接口,来实现接收、发送、连接断开和异常处理等操作,目前上面提到的这四个Handler都是在IO线程池中按顺序被调用,但HeartbeatHandler调用后下一个Handler是?这时候就要Dispatcher来上场了,Dispatcher是dubbo中的调度器,用来决定操作是在IO中执行还是业务线程池执行,来一张官方的图(http://dubbo.io/user-guide/demos/线程模型.html):

上图Dispatcher后面跟着的ThreadPool就是我们所说的业务线程池。Dispatcher分为5类,默认是all,解释也直接参考官方截图:

因为默认是all,所以包括请求、响应、连接、断开和心跳都交给业务线程池来处理,则无疑加大了业务线程池的负担,因为默认是200。每种Dispatcher,都有对应的ChannelHandler,ChannelHandler将Handler的调动形成调用链。如果配置的是all,那么接下来上场的就是AllChannelHandler;如果配置的是message,那么接下来上场的就是MessageOnlyChannelHandler,这些ChannelHandler都是WrappedChannelHandler的子类,WrappedChannelHandler默认把请求、响应、连接、断开、心跳操作都交给Handler来处理:
protected final ChannelHandler handler;
public void connected(Channel channel) throws RemotingException {
handler.connected(channel);
}
public void disconnected(Channel channel) throws RemotingException {
handler.disconnected(channel);
}
public void sent(Channel channel, Object message) throws RemotingException {
handler.sent(channel, message);
}
public void received(Channel channel, Object message) throws RemotingException {
handler.received(channel, message);
}
public void caught(Channel channel, Throwable exception) throws RemotingException {
handler.caught(channel, exception);
}
很显然,如果直接使用WrappedChannelHandler的处理方式,那么Handler的调用会在当前的线程中完成(这里是IO线程),我们看看AllChannelHandler内部实现:
public void connected(Channel channel) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try{
cexecutor.execute(new ChannelEventRunnable(channel, handler ,ChannelState.CONNECTED));
}catch (Throwable t) {
throw new ExecutionException("connect event", channel, getClass()+" error when process connected event ." , t);
}
}
public void disconnected(Channel channel) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try{
cexecutor.execute(new ChannelEventRunnable(channel, handler ,ChannelState.DISCONNECTED));
}catch (Throwable t) {
throw new ExecutionException("disconnect event", channel, getClass()+" error when process disconnected event ." , t);
}
}
public void received(Channel channel, Object message) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
} catch (Throwable t) {
throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
}
}
public void caught(Channel channel, Throwable exception) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try{
cexecutor.execute(new ChannelEventRunnable(channel, handler ,ChannelState.CAUGHT, exception));
}catch (Throwable t) {
throw new ExecutionException("caught event", channel, getClass()+" error when process caught event ." , t);
}
}
可以看出,AllChannelHandler覆盖了WrappedChannelHandler所有的关键操作,都将其放进到ExecutorService(这里指的是业务线程池)中异步来处理,但唯一没有异步操作的就是sent方法,该方法主要用于应答,但官方文档却说使用all时应答也是放到业务线程池的,写错了?这里,关键的地方来了,一旦业务线程池满了,将抛出执行拒绝异常,将进入caught方法来处理,而该方法使用的仍然是业务线程池,所以很有可能这时业务线程池还是满的,于是悲剧了,直接导致下游的一个HeaderExchangeHandler没机会调用,而异常处理后的应答消息正是HeaderExchangeHandler#caught来完成的,所以最后NettyHandler#writeRequested也没有被调用,Consumer只能死等到超时,无法收到Provider的线程池打满异常。
从上面的分析得出结论,当Dispatcher使用all时,一旦Provider线程池被打满,由于异常处理也需要用业务线程池,如果此时运气好,业务线程池有空闲线程,那么Consumer将收到Provider发送的线程池打满异常;但很可能此时业务线程池还是满的,于是悲剧,异常处理和应答步骤也没有线程可以跑,导致无法应答Consumer,这时候Consumer只能苦等到超时!
这也是为什么我们有时候能在Consumer看到线程池打满异常,有时候看到的确是超时异常。
为啥我们设置Dispatcher为message可以规避此问题?直接看MessageOnlyChannelHandler的实现:
public void received(Channel channel, Object message) throws RemotingException {
ExecutorService cexecutor = executor;
if (cexecutor == null || cexecutor.isShutdown()) {
cexecutor = SHARED_EXECUTOR;
}
try {
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
} catch (Throwable t) {
throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
}
}
没错,MessageOnlyChannelHandler只覆盖了WrappedChannelHandler的received方法,意味着只有请求处理会用到业务线程池,其他的非业务操作直接在IO线程池执行,这不正是我们想要的吗?所以使用message的Dispatcher,不会存在Provider线程池满了,Consumer却还在傻等的情况,因为默认IO线程池是无界的,一定会有线程来处理异常和应答(如果你把它设置成有界,那我也没啥好说的了)。
所以,为了减少在Provider线程池打满时整个系统雪崩的风险,建议将Dispatcher设置成message:
<!--StartFragment--> <!--EndFragment-->
<dubbo:protocol name="dubbo" port="8888" threads="500" dispatcher="message" />
dubbo的dispatcher设置原理的更多相关文章
- 【转】JVM 堆内存设置原理
堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...
- DUBBO监控,设置接口调用数据的上报周期
目录 DUBBO监控,设置接口调用数据的上报周期 dubbo已有的监控方案 针对已有方案的改进 DUBBO监控,设置接口调用数据的上报周期 dubbo是目前比较好用的,用来实现soa架构的一个工具,d ...
- [转]JVM 堆内存设置原理
堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...
- JVM 堆内存设置原理
堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...
- JVM 堆内存设置原理(转)
堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...
- Dubbo之服务消费原理
前言 上篇文章<Dubbo之服务暴露>分析 Dubbo 服务是如何暴露的,本文接着分析 Dubbo 服务的消费流程.主要从以下几个方面进行分析:注册中心的暴露:通过注册中心进行服务消费通知 ...
- Dubbo的优雅下线原理分析
文/朱季谦 Dubbo如何实现优雅下线? 这个问题困扰了我一阵,既然有优雅下线这种说法,那么,是否有非优雅下线的说法呢? 这,还真有. 可以从linux进程关闭说起,其实,我们经常使用到杀进程的指令背 ...
- Dubbo的使用及原理浅析.
前面几个博文中关于SSM 框架已经搭建完成, 这里来讲下项目中使用到的Dubbo以及自己了解到的关于Dubbo的一些知识. Dubbo是什么? Dubbo是阿里巴巴SOA服务化治理方案的核心框架,每天 ...
- dubbo超时优先级设置
调用超时配置的优先级 可以在多个配置项设置超时,由上至下覆盖(即上面的优先),示例如下: # 其它的参数(retries.loadbalance.actives等)的覆盖策略也一样. 提供者端特定方法 ...
随机推荐
- JAVA中fail-fast机制
在JDK的Collection中我们时常会看到类似于这样的话: 例如,ArrayList: 注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证.快速失 ...
- 旋转坐标+前缀和(zqu 25001)
本题题意:在一个矩阵中,去随机一点,设定一个步数K,求出从这个点可以走到的范围的和,求最大值 思路:这个范围的和是一个菱形,我们把他旋转45°,然后成为一个正放的矩阵,求出二维前缀和 然后用前缀和的性 ...
- 040_字符串连接符 041_条件运算符目 042_运算符优先级_逻辑与或优先问题 043_自动类型转化 044_强制类型转换 045_基本类型常见错误_溢出_L问题
040_字符串连接符 package test_package; /** * 字符串运算符 * @author * */public class TestOperator05 { public sta ...
- 数据提取之JSON与JsonPATH
数据提取之JSON与JsonPATH JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使得人们很容易的进行阅读和编写.同时也方便了机器进行解析和生成.适 ...
- SOCV / POCV 模型 (3)
STA无疑是数字集成电路设计实现方法学中最『漂亮』的模型之一,但是随意着工艺进步,local varition 的随机性及重要性增加,传统STA 的局限性日渐突出.大概在十五年前,SSTA成了一个研究 ...
- c# 异常:值不能为 null。 参数名: source
异常详细信息: System.ArgumentNullException: 值不能为 null.参数名: source 其实问题那就出在 Select() 方法,在 Select 上按 F12 查看定 ...
- 题解 CF492C Vanya and Exams
CF492C Vanya and Exams 有了Pascal题解,来一波C++题解呀qwq.. 简单的贪心题 按b[i]从小到大排序,一个一个学科写直到达到要求即可 #include<cstd ...
- 简单桶排序(Bucket Sort)
1.基本思想 桶排序是将待排序集合中处于同一个值域的元素存放在同一个桶中1. 2.算法设计2 假设有一个班级有5个人,这次期末他们分别考了5分,2分,4分,5分,8分(满分为10分).需要将这些分数从 ...
- cookie的封装
今天逛论坛,看到一个看起来写得好的函数,特此贴出分享: 原文地址[http://www.html-js.com/article/2638 ] 这个地址[https://github.com/jaywc ...
- 【Math】高数-一个有趣的旋转体体积与面积
Go confidently in the direction of your dreams. Live the life you've imagined. 题目 设曲线 \(y = \tfrac{1 ...