当我们聊dubbo线程模型&线程池策略的时候,我们应该考虑哪些问题?
- dubbo有哪些功能性线程池?(基于netty通信reactor响应器模式的boss、work两个线程池&业务自己定义的线程池)
- dubbo如何利用这些线程池构成不同线程模型?结合适用场景分析
- 如何自定义线程模型&线程池策略(和服务降级策略、集群容错、负载均衡策略扩展方式一致)
dubbo底层网络通讯默认使用的是netty,服务提供方NettyServer使用两级线程池,其中 EventLoopGroup(boss) 主要用来接受客户端的链接请求,并把接受的请求分发给 EventLoopGroup(worker) 来处理,boss和worker线程组我们称之为IO线程。
- 如果服务提供方的逻辑能迅速完成,并且不会发起新的IO请求,那么直接在IO线程上处理会更快,因为这减少了线程池调度。
- 但如果处理逻辑很慢,或者需要发起新的IO请求,比如需要查询数据库,则IO线程必须派发请求到新的线程池进行处理,否则IO线程会阻塞,将导致不能接收其它
Dubbo提供的线程模型
根据请求的消息类被IO线程处理还是被业务线程池处理,Dubbo提供了下面几种线程模型:
-
all : (AllDispatcher类)所有消息都派发到业务线程池,这些消息包括请求/响应/连接事件/断开事件/心跳等,这些线程模型如下图:(dubbo默认的线程模型)
- direct : (DirectDispacher类)所有消息都不派发到业务线程池,全部在IO线程上直接执行,模型如下图:
- message : (MessageOnlyDispatcher类)只有请求响应消息派发到业务线程池,其他连接断开事件/心跳等消息,直接在IO线程上执行,模型图如下:
- execution:(ExecutionDispatcher类)只把请求类消息派发到业务线程池处理,但是响应和其它连接、断开事件,心跳等消息直接在IO线程上执行,模型如下图:
- connection:(ConnectionOrderedDispatcher类)在IO线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到业务线程池处理,模型如下图:
AllDispatcher源码剖析
all线程模型的实现如下:
/**
* default thread pool configure
*
*/
public class AllDispatcher implements Dispatcher {
public static final String NAME = "all";
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
return new AllChannelHandler(handler, url);
}
}
其中核心AllChannelHandler把所有事件都交给业务线程池去处理,对应的代码如下:
/**
* `all` 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
*/
public class AllChannelHandler extends WrappedChannelHandler {
public AllChannelHandler(ChannelHandler handler, URL url) {
super(handler, url);
}
// 连接完成事件交给业务线程池处理
@Override
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);
}
}
// 请求响应事件,交给业务线程池处理
@SuppressWarnings("Duplicates")
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) {
//TODO A temporary solution to the problem that the exception information can not be sent to the opposite end after the thread pool is full. Need a refactoring
//fix The thread pool is full, refuses to call, does not return, and causes the consumer to wait for time out
if(message instanceof Request && t instanceof RejectedExecutionException){
Request request = (Request)message;
if(request.isTwoWay()){
String msg = "Server side(" + url.getIp() + "," + url.getPort() + ") threadpool is exhausted ,detail msg:" + t.getMessage();
Response response = new Response(request.getId(), request.getVersion());
response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR);
response.setErrorMessage(msg);
channel.send(response);
return;
}
}
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);
}
}
private ExecutorService getExecutorService() {
ExecutorService cexecutor = executor;
if (cexecutor == null || cexecutor.isShutdown()) {
cexecutor = SHARED_EXECUTOR;
}
return cexecutor;
}
}
Dubbo提供了常用的线程池模型,这些模型可以满足我们绝大多数的需求,但是您可以根据自己的需要进行扩展定制。在服务提供者启动线程时,我们会看到什么时候加载的线程模型的实现。
Dubbo提供的线程池策略
扩展接口 ThreadPool 的SPI实现有如下几种:
- fixed:固定大小线程池,启动时建立线程,不关闭,一直持有(缺省)。
- cached:缓存线程池,空闲一分钟自动删除,需要时重建。
- limited:可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然带来大流量引起性能问题。
其中fixed策略对应扩展实现类是FixedThreadPool,代码如下:
/**
* ThreadPool
*
* 线程池接口
*/
@SPI("fixed")
public interface ThreadPool {
/**
* Thread pool
*
* @param url URL contains thread parameter
* @return thread pool
*/
@Adaptive({Constants.THREADPOOL_KEY})
Executor getExecutor(URL url);
}
public class FixedThreadPool implements ThreadPool{
public Executor getExecutor(URL url){
String name = url.getParameter(Constants.THREAD_NAME_KEY,Constants.DEFAULT_THREAD_NAME);
// 默认大小为200
int threads = url.getParameter(Constants.THREADS_KEY,Constants.DEFAULT_THREADS);
int queues = url.getParameter(Constants.QUEUES_KEY,Constants.DEFAULT_QUEUES);
return new ThreadPoolExecutor(threads , threads , 0 , TimeUnit.MILLISECONDS , queues==0 ? new SynchronousQueue<Runnable>() : (queue < 0 ? new LinkedBlockingQueue<Runnable>(queues)) , new NamedThreadFactory(name,true) , new AbortPolicyWithReport(name,url));
}
}
可知使用ThreadPoolExecutor创建了核心线程数=最大线程池数=threads的线程池。
Dubbo线程池扩展,这些扩展可以满足我们绝大多数的需求,但是您可以根据自己的需要进行扩展定制。在服务提供者启动流程时,我们会看到什么时候加载的线程池扩展实现。
何时确定使用何种线程模型&线程池策略
服务提供方会启用NettyServer来监听消费方的连接,构造函数如下:
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
这里主要看ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)))这个代码,该代码加载了具体线程模型,是通过ChannelHandlers的wrapInternal方法完成:
/**
* 通道处理器工厂
*/
public class ChannelHandlers {
/**
* 单例
*/
private static ChannelHandlers INSTANCE = new ChannelHandlers();
protected ChannelHandlers() {
}
public static ChannelHandler wrap(ChannelHandler handler, URL url) {
return ChannelHandlers.getInstance().wrapInternal(handler, url);
}
protected static ChannelHandlers getInstance() {
return INSTANCE;
}
static void setTestingChannelHandlers(ChannelHandlers instance) { // for testing
INSTANCE = instance;
}
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
// 根据url里的线程模型,来通过SPI机制具体的Disptcher扩展实现类
return new MultiMessageHandler(
new HeartbeatHandler(
ExtensionLoader.getExtensionLoader(Dispatcher.class).getAdaptiveExtension().dispatch(handler, url)
)
);
}
}