Overview
了解本部分请先参考: Dubbo User Book - 6.4 线程模型 · GitBook
相关代码位于 dubbo-remoting-api
Call stack
如果你曾经在Provider端的服务实现类中打过断点, 会发现调用是从ChannelEventRunnable.run()
开始的, 这是因为默认情况下, Provider端的业务调用都是在一个单独的线程池中执行的, 即WrappedChannelHandler
中的executor
.
从IO线程中获取的数据分发到业务线程池, 这一功能在Dubbo被抽象成了Dispatcher
:
1 | ChannelHandler dispatch(ChannelHandler handler, URL url); |
根据此类上的@SPI(AllDispatcher.NAME)
也可知默认为AllDispatcher
. 实际的逻辑封装在AllDispatcher
中返回的AllChannelHandler
. 在其中的received
方法中打断点, 调用栈如下:
1 | NettyServer.received(Channel, Object) |
由此Provider端调用栈即可形成一个闭环: 从Netty -> 具体的业务实现.
线程模型 - Dispatcher
除了DirectDispatcher
之外, 每一种配置都对应了一种ChannelHandler
的实现类. 他们都是WrappedChannelHandler
的子类.
ChannelHandler
在Dubbo中很重要, 还有很多相关的子类暂时不需要关注. 我们先来看一下: DirectDispatcher
和AllDispatcher
DirectDispatcher
最简单, 它直接返回了传入的ChannelHandler
, 这个类即为DecodeHandler
. 联系上面的调用栈可知下一步调用即为DecodeHandler#received
AllDispatcher
覆盖了WrappedChannelHandler
的几乎所有方法, 都是实例化一个ChannelEventRunnable
, 交给executor
执行. 在ChannelEventRunnable
的run
方法中再执行DecodeHandler
的后续操作.
如果了解过Dubbo的其他代码, 可以发现这又是常见的装饰模式. 四个类对后续的ChannelHandler
(实际上是DecodeHandler
)进行装饰, 实现内容实际上比较简单.
这里有个值得注意的问题是, execution 这一策略, 并没有实现. 具体实现跟文档描述并不一致. 可以参考dispatcher:execution · Issue #1089 · apache/incubator-dubbo · GitHub 以及 fixes #1089, make ExecutionDispatcher meet dubbo-user-book by qct · Pull Request #1449 · apache/incubator-dubbo · GitHub
线程池配置
线程池的实例化在WrappedChannelHandler
中:
1 | executor = (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url); |
默认情况下使用FixedThreadPool
, 位于dubbo-commons
1 | public class FixedThreadPool implements ThreadPool { |
可见默认情况下, 创建一个大小为200的线程池; 使用队列SynchronousQueue
, 即可看作没有使用队列缓存元素. 并在饱和时抛出异常, 同时jstack
日志到文件(AbortPolicyWithReport
) .
你或许需要使用MessageOnlyDispatcher
:
如果Dubbo线程池占满时, 很可能发现调用端获得的是超时异常.
注意上文提到的WrappedChannelHandler
中有一个caught
方法, 当发生异常时(暂时不需要了解此处的调用关系)会被执行. 参考下方See Also第一篇, 使用AllChannelHandler
策略时, caught
方法由于线程池满也被拒绝, 导致无法返回, 调用端只能等到Timeout.
另外一个问题是, AllDispatcher
中如果判断executor
为空或者已经shutdown
, 会去拿SHARED_EXECUTOR
. 这种场景并没有想到何时回出现, 希望知道的朋友指点.
Call Stack 上游
1 | protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) { |
省略了部分调用:
1 | ServiceBean.doExport() |