`
manzhizhen
  • 浏览: 293744 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Dubbo源代码分析八:再说Provider线程池被EXHAUSTED

阅读更多

         在上回《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" />

  • 大小: 129.8 KB
  • 大小: 23 KB
1
0
分享到:
评论
5 楼 manzhizhen 2018-01-28  
wodemeili123 写道
楼主,你在《Dubbo源代码实现六:线程池模型与提供者》都提到了“Dubbo选择的是“无边界”的CachedThreadPool,这意味着对所有服务请求先做到“来者不拒”,但它进一步限制了IO处理的线程数,默认是核数+1”,那么最后的结论“Consumer却还在傻等的情况,因为默认IO线程池是无界的,一定会有线程来处理异常和应答”不是自相矛盾吗


你很细心,这描述确实有问题,特意去看了下NioServerSocketChannelFactory的源码,默认内部使用的work数是核数*2,而不是无界,Consumer在傻等,具体原因本文已经说了,还有疑问可以扣扣沟通:2323173088,相互交流学习
4 楼 wodemeili123 2017-12-15  
楼主,你在《Dubbo源代码实现六:线程池模型与提供者》都提到了“Dubbo选择的是“无边界”的CachedThreadPool,这意味着对所有服务请求先做到“来者不拒”,但它进一步限制了IO处理的线程数,默认是核数+1”,那么最后的结论“Consumer却还在傻等的情况,因为默认IO线程池是无界的,一定会有线程来处理异常和应答”不是自相矛盾吗
3 楼 yingwuluohan 2017-09-10  
多谢楼主的建议
dubbo官网最近改版啦,貌似阿里重新维护了
2 楼 manzhizhen 2017-09-06  
秒杀系统的限流需要页面和服务端配合来做,有时候甚至需要单独的集群来做,避免影响其他核心业务,但如果简单处理的话,也不应该让数据库来承受压力,用分布式缓存或内存做一个有限队列就行,让“最先”到的几个请求有机会争抢最终的商品
1 楼 yingwuluohan 2017-09-05  
楼主,我又来啦!
最近遇到一个数据同步的问题,业务场景是这样的:电商里面经常会搞些促销和秒杀的活动,单位时间读库和写库并发都很高,比如说商品秒杀的时候。这个时候数据库如果是主从配置的话怎么样能有效的保证主从数据的及时同步呢?经常会遇到主从数据不一致的问题
楼主有能不能提供一个思路之类的想法

相关推荐

    Dubbo源代码(2.8.4)

    本文将深入探讨Dubbo 2.8.4的源代码结构、核心组件及其工作原理。 1. **源代码结构**: Dubbo的源码结构清晰,主要包括以下几个模块: - `dubbo-common`:基础通用模块,包含各种工具类和公共接口。 - `dubbo-...

    Dubbo源代码(2.5.4)

    【Dubbo源代码(2.5.4)】是一份重要的开源项目资源,它包含了Dubbo框架在2.5.4版本的完整源代码。Dubbo是中国阿里巴巴公司贡献的高性能、轻量级的服务治理框架,它专注于服务调用、监控和服务治理。这个版本的源...

    Apache Dubbo:Dubbo高级特性:服务降级与熔断实战

    Apache Dubbo:Dubbo高级特性:服务降级与熔断实战 Dubbo是著名的RCP框架,文档内有干货,提供代码和可复现的命令,值得借鉴。

    Dubbo源代码分析之远程调用过程(2.5.4开发版)

    该文档分析了 Dubbo 框架中 RPC 调用的整个流程,并基于源代码按照执行 时序进行说明,源码版本为2.5.4开发版。 涉及的关键点包括:Invocation、Invoker、Directory、路由、负载均衡、集群容错、过滤器以及监控模块...

    Apache Dubbo:Dubbo服务治理:服务熔断与超时重试

    ### Apache Dubbo:服务熔断与超时重试 #### 一、服务熔断基础 ##### 1.1 服务熔断的概念 服务熔断,作为一种重要的服务稳定性保障措施,在分布式系统中扮演着至关重要的角色。它的工作原理是,当某个服务节点...

    Apache Dubbo:Dubbo监控与运维:服务性能分析

    ### Apache Dubbo:Dubbo监控与运维:服务性能分析 #### 一、Dubbo监控概述 ##### 1.1 Dubbo监控的重要性 在现代微服务架构中,由于服务之间存在着复杂的调用关系,任何单一服务的性能问题都有可能对整体系统稳定...

    Apache Dubbo:Dubbo服务治理:服务路由与动态配置

    ### Apache Dubbo:服务治理——服务路由与动态配置 #### 一、服务治理的重要性 在当前流行的微服务架构中,由于各个服务之间存在着频繁而复杂的交互,如何有效地管理和控制这些服务成为了确保整个系统稳定性和可...

    Apache Dubbo:Dubbo服务治理:负载均衡与容错机制

    ### Apache Dubbo:Dubbo服务治理:负载均衡与容错机制 #### 一、Dubbo服务治理的重要性 在现代微服务架构中,服务间的交互变得日益频繁和复杂,因此需要一种有效的方式来管理这些服务,以确保整个系统的稳定性和...

    Apache Dubbo:Dubbo核心概念:服务提供者与消费者

    ### Apache Dubbo:服务提供者与消费者核心概念详解 #### 一、Apache Dubbo概览 **Apache Dubbo**是一款高性能、轻量级的开源微服务框架,最初由阿里巴巴内部开发并在2008年开始使用,随后在2011年开源。自2017年...

    dubbo xsd的支持

    6. `&lt;dubbo:provider&gt;` 和 `&lt;dubbo:consumer&gt;`:这两个元素是服务提供者和服务消费者的高级配置,可以用来设置一些全局的属性。 7. `&lt;dubbo:method&gt;` 和 `&lt;dubbo:argument&gt;`:细化服务方法和参数的配置,如异步调用...

    dubbo2.8.4.jar

    如果使用dubbo遇到错误:com.alibaba.dubbo.remoting.RemotingException: Fail to decode request due to: RpcInvocation 请下载这个jar,替换掉你项目中的那个jar,应该可以解决。

    Apache Dubbo:Dubbo高级特性:服务版本与分组

    - **配置文件方式**:在服务提供者和消费者的配置文件中,使用 `&lt;dubbo:service&gt;` 或 `&lt;dubbo:reference&gt;` 标签的 `version` 属性来指定服务版本。 - **服务提供者配置**: ```xml &lt;dubbo:service interface=...

    dubbo源代码

    dubbo分布式服务框架,方便大家对分布式服务的学习,方便对dubbo的扩展

    dubbo示例代码dubbo-sample

    例如,在`dubbo-samples-api`模块中定义的接口,会在`dubbo-samples-provider`模块中被具体实现并暴露。 2. **服务消费者(Consumer)** 服务消费者则是调用服务的组件,通过`@Reference`注解引用服务提供者提供的...

    dubbo-Consumer,dubbo-provider

    在本项目中,`dubbo-consumer`模块可能包含了消费者端的代码,用于通过Dubbo框架来远程调用`dubbo-provider`提供的服务。它通常会定义服务接口,并在运行时通过Dubbo的注册中心找到服务提供者的地址,然后进行RPC...

    dubbo从入门到精通教程

    从服务模型的角度来看,Dubbo采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务,所以基于这一点可以抽象出服务提供方(Provider)和服务消费方(Consumer)两个角色。 dubbo服务的主要...

    Spring+mybatis+dubbo整合源代码及jar包

    通过Spring_Mybatis_Dubbo_Jars.zip和Spring_Mybatis_Dubbo2.zip这两个压缩包,你可以获取到整合所需的所有依赖包和源代码,从而快速搭建起这个框架。这将帮助开发者更快地理解并掌握这三大框架的协同工作方式,提高...

    Dubbo 27道面试题及答案.docx

    Dubbo 的核心配置有:dubbo:service、dubbo:reference、dubbo:protocol、dubbo:registry、dubbo:application、dubbo:provider、dubbo:consumer、dubbo:method 等。 Dubbo 推荐使用 Dubbo 协议。 Dubbo 在安全机制...

    dubbo2.8.4源代码

    【标题】"Dubbo 2.8.4 源代码" 涵盖了分布式服务框架的核心技术,是阿里巴巴开源的一款高性能、轻量级的服务治理框架。它为开发者提供了微服务开发所需的诸多功能,包括服务注册与发现、负载均衡、调用链路监控等。...

    dubbo server+client 完整代码

    【Dubbo Server+Client 完整代码】是一个深入学习和实践Dubbo框架的实例项目,它涵盖了服务端(Server)和服务消费者端(Client)的完整实现。Dubbo是阿里巴巴开源的一款高性能、轻量级的Java远程服务框架,它强调了...

Global site tag (gtag.js) - Google Analytics