大家好,今天开始给大家分享 — Dubbo 专题之 Dubbo 线程池模型。在前面上个章节中我们讨论了 Dubbo SPI,了解了 Dubbo SPI 其本质是从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来,同时解决了 Java 中 SPI 的一些缺陷。以及我们使用 Dubbo SPI 实现自定义能力的拓展。那本章节我们要讨论的 Dubbo 线程模型也是基于 SPI 实现,那什么是线程模型呢?以及其在我们的项目中有什么作用呢?那么我们在本章节中进行讨论。下面就让我们快速开始吧!
小伙伴如果对 Servlet
熟悉就知道,从 Servlet 3.x
开始支持异步非阻塞模式。至于什么异步非阻塞前面我在前面的章节中有讨论小伙伴可以自行学习之前的文章。我们通过一个访问Web应用流程图简单说明:
在上面的流程图中我们可以看到第一个请求发起同步 Web 调用,然后 Web 再发起对第三方服务的调用,整个过程全链路是同步调用。第二个请求同样也是发起同步调用,但是在发起第三方调用的时候切换了线程(基于 Servlet 3.x
我们不需要手动的创建线程来切换)。这么做的好处在于我们可以用专门处理线程池去做业务处理或第三方服务的调用。那什么情况下我们需要切换线程不使用主线程呢?如果事件处理的逻辑能迅速完成,并且不会发起新的 IO
请求,比如只是在内存中记个标识,则直接在 IO
线程上处理更快,因为减少了线程池调度。但如果事件处理逻辑较慢,或者需要发起新的 IO 请求
,比如需要查询数据库或其它服务调用时,则必须派发到线程池,否则 IO
线程阻塞,将导致不能接收其它请求。
那在 Dubbo 中给我们提供了通过不同的派发策略和不同的线程池配置的组合来应对不同的场景。配置方式如下:
<dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />
下面我们简单描述下dispatcher
和threadpool
的参数说明:
Dispatcher
all
所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。(默认)
direct
所有消息都不派发到线程池,全部在 IO 线程上直接执行。
message
只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
execution
只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
connection
在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
ThreadPool
fixed
固定大小线程池,启动时建立线程,不关闭,一直持有。(默认)
cached
缓存线程池,空闲一分钟自动删除,需要时重建。
limited
可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
eager
优先创建Worker
线程池。在任务数量大于corePoolSize
但是小于maximumPoolSize
时,优先创建Worker
来处理任务。当任务数量大于maximumPoolSize
时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException
。(相比于cached
:cached
在任务数量超过maximumPoolSize
时直接抛出异常而不是将任务放入阻塞队列)
通过前面的介绍我们应该明白我们为什么需要切换线程,遵循一个很简单的原则:如果我们处理的任务需要操作新的 IO 或者处理任务需要很长的时间那么我们就可以把这部分工作放到我们的任务线程池去处理。那么我们简单的总结下在工作常遇到的场景:
计算型服务:在我之前的工作中遇到这样的一个需求:我们的车机实时上报数据给服务器,服务器记录数据并且实时计算和纠正导航数据。那么这里我们需要一个计算型的微服务,主要的工作就是计算和修正实时数据,那么这个服务就是典型的计算型服务,所有我们计算过程中尽量减少线程的切换并尽可能的在一个线程内进行计算。这样减少线程切换的开销提供计算速度。
网关服务:首先我们需要了解什么是网关,简单的理解就是所有的服务入口,对每个服务的调用必须经过网关转发到对应服务上(类似 Nginx
)。那这里网关主要工作就是服务转发(鉴权、限流等等),可以理解为发起请求。很明显发起请求就是开启新的 IO
所有我们可以切换到线程池去处理。
下面我们通过以获取图书列表为例进行演示。以下是项目的结构图:
因为这里我们主要是对服务提供端的配置,所有我们主要看dubbo-provider-xml.xml
配置内容:
上面的 XML 配置中dispatcher="all"
指定事件的分发策略、threadpool="fixed" threads="100"
指定线程池固定大小为100
。
这里分发策略和线程池采用 Dubbo
中的 SPI 方式加载的小伙伴可以参考前面的 《Dubbo SPI》章节进行了解。下面我们进入主题,首先看看在 Dubbo 中为我们提供的5种事件分发策略:
我们这里简单的分析 all
分发策略其它的都是类似的小伙伴自行查阅源码分析。下面我们看看org.apache.dubbo.remoting.transport.dispatcher.all.AllChannelHandler
核心源码:
?
/***
*@className AllChannelHandler
*
*@description 所有处理分发到线程池去处理
*
*@author <a href="http://youngitman.tech">青年IT男</a>
*
*@date 12:50 2020-03-05
*
*@JunitTest: {@link }
*
*@version v1.0.0
*
**/
public class AllChannelHandler extends WrappedChannelHandler {
?
public AllChannelHandler(ChannelHandler handler, URL url) {
super(handler, url);
}
?
/**
*
* 远程连接事件回调
*
* @author liyong
* @date 1:34 PM 2020/12/6
* @param channel
* @exception
* @return void
**/