dubbo的线程模型、派发策略、线程池策略

Dubbo的线程模型由Netty的IO线程和自定义线程池组成,派发策略包括AllDispatcher、DirectDispatcher、ConnectionOrderedChannelHandler、ExecutionChannelHandler和MessageOnlyChannelHandler。不同策略决定请求是交给线程池处理还是IO线程处理。例如,DirectDispatcher将所有请求交给IO线程,而ExecutionChannelHandler仅派发请求到线程池。线程池策略包括cache、limit、fix和eager,分别对应不同场景的使用需求。

dubbo提供了自己的线程池以及派发策略,其实官方文档上讲的是比较清楚的,只需要结合着源码看下,就能明白具体的原理
dubbo官方文档地址

线程模型

在这里插入图片描述

用简单的总结来说,dubbo中的线程模型是有netty本身的io线程 + dubbo提供的线程池组成的,也就是说,不管是正常的请求、还是响应、还是连接请求、断开连接请求,要么是有netty的io线程来执行,要么是线程池中的线程来执行
不同的派发策略,会有不同的处理效果

派发策略以及源码

根据官网的解释,有以下五种派发策略,在自己去学习这五个策略的时候,我一直不明白是如何实现有些请求可以交给线程池去处理,有些请求会交给io线程去处理,看了源码之后,就理解了
在这里插入图片描述
问题:
1、首先有五个派发策略,其中有四个都继承了WrappedChannelHandler, 为什么单单direct没有继承?
2、是如何实现请求是要到线程池中?还是到io线程中执行呢?
先回答第二个问题,wrappedChannelHandler是一个handler,是会被netty执行的,所以,如果我们没有做其他处理,默认执行wrappedChannelHandler中的方法,那其实就是把请求交给了netty的io线程去执行
在dubbo中,五个dispatcher类,通过覆写wrappedChannelHandler的方法,来决定是要交给线程池执行?还是io线程执行
也就是说,如果连接事件,要交给线程池执行,那就覆写connect()方法,在子类的connect方法中,将请求交给线程池即可
如果断开连接事件,要交给io线程执行,就不需要在子类中覆写disConnect()方法

WrappedChannelHandler#getExecutorService()

这里先插一段代码,因为这段代码是前提,就是根据dubbo的自适应扩展机制以及我们的配置,来获取一个线程池,默认是fix

public WrappedChannelHandler(ChannelHandler handler, URL url) {
    this.handler = handler;
    this.url = url;
    //初始化线程池,根据adaptive机制,获取dubbo提供的线程池
    executor = (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url);

    String componentKey = Constants.EXECUTOR_SERVICE_COMPONENT_KEY;
    if (Constants.CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(Constants.SIDE_KEY))) {
        componentKey = Constants.CONSUMER_SIDE;
    }
    DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
    dataStore.put(componentKey, Integer.toString(url.getPort()), executor);
}


// 获取线程池
public ExecutorService getExecutorService() {
    ExecutorService cexecutor = executor;
    if (cexecutor == null || cexecutor.isShutdown()) {
        cexecutor = SHARED_EXECUTOR;
    }
    return cexecutor;
}
AllDispatcher

所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
可以看到,这个dispatch的类,覆写了父类的所有方法,在子类的实现方法中,都是调用的cexecutor.execute()去提交的任务
getExecutorService()就是获取当前程序员配置的要使用的线程池,如果不配置,默认是fix

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

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

/**
 * 这里是关键,服务端在接收到请求之后,会把请求交给一个线程池来处理
 * @param channel
 * @param message
 * @throws RemotingException
 */
@Override
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);
    }
}

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

所有消息都不派发到线程池,全部在 IO 线程上直接执行
direct更简单,干脆就没有实现directChannelHandler类,在其dispatch方法中,直接将入参的handler返回

public ChannelHandler dispatch(ChannelHandler handler, URL url) {
    return handler;
}

所以,我们可以理解为,direct策略,会将所有请求都交给io线程去处理

ConnectionOrderedChannelHandler

在 IO 线程上,将连接、断开事件放入队列,有序逐个执行,其它消息派发到线程池。
这个方法,只有一个比较特殊的点,其他都和上面allDispatcher差别不大;
我一直疑问这里为什么是有序逐个执行

@Override
public void connected(Channel channel) throws RemotingException {
    try {
        checkQueueLength();
        connectionExecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CONNECTED));
    } catch (Throwable t) {
        throw new ExecutionException("connect event", channel, getClass() + " error when process connected event .", t);
    }
}

原因就在这里的connectionExecutor;

public ConnectionOrderedChannelHandler(ChannelHandler handler, URL url) {
    super(handler, url);
    String threadName = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
    connectionExecutor = new ThreadPoolExecutor(1, 1,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(url.getPositiveParameter(Constants.CONNECT_QUEUE_CAPACITY, Integer.MAX_VALUE)),
            new NamedThreadFactory(threadName, true),
            new AbortPolicyWithReport(threadName, url)
    );  // FIXME There's no place to release connectionExecutor!
    queuewarninglimit = url.getParameter(Constants.CONNECT_QUEUE_WARNING_SIZE, Constants.DEFAULT_CONNECT_QUEUE_WARNING_SIZE);
}

也就是说,这里又new了一个新的线程池,线程池的核心线程数、最大线程数只有一个,其他的,就需要进入到阻塞队列中进行等待

ExecutionChannelHandler

只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。

@Override
public void received(Channel channel, Object message) throws RemotingException {
    ExecutorService cexecutor = getExecutorService();
    if (message instanceof Request) {
        try {
            cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
        } catch (Throwable t) {
            // FIXME: when the thread pool is full, SERVER_THREADPOOL_EXHAUSTED_ERROR cannot return properly,
            // therefore the consumer side has to wait until gets timeout. This is a temporary solution to prevent
            // this scenario from happening, but a better solution should be considered later.
            if (t instanceof RejectedExecutionException) {
                Request request = (Request) message;
                if (request.isTwoWay()) {
                    String msg = "Server side(" + url.getIp() + "," + url.getPort()
                            + ") thread pool 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);
        }
    } else {
        handler.received(channel, message);
    }
}

这里可以看到,覆写了received方法之后,还在方法中,判断是否是request事件,如果是请求事件,就交给dubbo的线程池去处理,反之,交给netty的handler处理

MessageOnlyChannelHandler

只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。

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

这里就比较简单了,直接覆写received方法,无论是request请求,还是response请求,都交给dubbo的线程池去处理

线程池

dubbo中所提供的线程池,其实就是基于jdk的线程池,自己定义了四个线程池

cache

缓存线程池,空闲等待时间是1分钟,超过1分钟,没有任务提交,就销毁线程,设置的核心线程数是0

return new ThreadPoolExecutor(cores, threads, alive, TimeUnit.MILLISECONDS,
                queues == 0 ? new SynchronousQueue<Runnable>() :
                        (queues < 0 ? new LinkedBlockingQueue<Runnable>()
                                : new LinkedBlockingQueue<Runnable>(queues)),
                new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
limit

可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
设置的cores默认是0,threads默认是200,空闲等待时间是无限制,可以理解为没有拉取到排队的任务,就一直等待,也不会销毁线程
所以这个线程池是只会增长,不会收缩

return new ThreadPoolExecutor(cores, threads, Long.MAX_VALUE, TimeUnit.MILLISECONDS,
                queues == 0 ? new SynchronousQueue<Runnable>() :
                        (queues < 0 ? new LinkedBlockingQueue<Runnable>()
                                : new LinkedBlockingQueue<Runnable>(queues)),
                new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
fix

核心线程数、最大线程数都是200,空闲等待时间是0

// 这里的threads默认是200
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));
eager

TODO

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值