前言
前面用了 7 篇文章 循序渐进 讲解了 Dubbo SPI 的使用方法与应用,为今后分析 Dubbo 源码与执行流程 奠定了基础。接下来会通过 3-4 篇文章讲解 Dubbo 服务远程暴露流程。
先来看下阅读本文的基础:
- 对 Dubbo SPI 有一定的了解
- 对 Dubbo 的使用有所了解
再看几个说明
- 本文内容全部基于 Dubbo 2.7.x 的源码
- 文章内容是通过 debug 的调试源码的方式提炼核心步骤, 直接定位到重点
- 建议按照文章说明用 debug 跟踪下源码
正文
Dubbo 整个服务暴露流程很复杂,要经过很多类 和 方法,并且大部分方法还很长,我们肯定不能一行一行代码去剖析。
所以本文的目的是把整个流程中 最核心的步骤 单独拿出来,做一个简易的时序图, 因此分析的更加 粗略。
1. 服务暴漏入口
Dubbo 远程服务暴露的 隐藏入口 是 ServiceConfig#export()
方法, 不管你是用 API 还是 Spring 的方式去启动服务。
如下为 整个方法的源码,我们上面有提到我们需要从冗长的方法中 提取 核心步骤,而我们下面的核心步骤就是 doExport()
方法
public synchronized void export() {
// 是否需要暴露
if (!shouldExport()) {
return;
}
if (bootstrap == null) {
bootstrap = DubboBootstrap.getInstance();
bootstrap.init();
}
checkAndUpdateSubConfigs();
//init serviceMetadata
serviceMetadata.setVersion(version);
serviceMetadata.setGroup(group);
serviceMetadata.setDefaultGroup(group);
serviceMetadata.setServiceType(getInterfaceClass());
serviceMetadata.setServiceInterfaceName(getInterface());
serviceMetadata.setTarget(getRef());
// 是否延迟暴露
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
doExport();
}
exported();
}
再次强调: 很明显上面有很长一段代码, 但我们关注的重点就只有 doExport
方法, 其他的暂时不用管, 后面的文章会补充.
2. 核心流程
运行 Dubbo 官方 demo(或者源码中的测试用例等等),目的是将服务注册到 zookeeper 上(你也可以用其他注册中心),然后通过 debug 模式运行代码,初始断点就打在 ServiceConfig#export()
方法上,然后一步一步跟下去,你就能梳理出下面的时序图(至少可以跟踪完图中ServiceConfig
的方法)。
3. 源码分析
上面的时序图中涉及到 4 个类,我们一个一个来看。
3.1 ServiceConfig
通过 debug 从初始断点ServiceConfig#export()
一步一步往下跟踪,前 5 步都是肉眼可见的,具体代码就不贴了, 但是这里需要讲一下,为什么第 5 步调用了 PROTOCOL.export()
方法就突然跳到 RegisterProtocol#export()
方法。
来看一下
PROTOCOL
的相关定义:
# 去除了一些修饰符
Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
而 Protocol 是一个扩展类,这个前面的文章中详细举例说明过。
@SPI("dubbo")
public interface Protocol {
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
}
综上所得,第 5 步用到了 Dubbo SPI 的自适应扩展机制。
根据我们之前讲的可以得知:在调用 export()
方法时,会动态生成一个Protocol$Adaptive
, 然后根据传入的参数(要么是URL,要么是URL的包装类),取出对应的 key,比如 key 对应的值为 dubbo,那么获得扩展类就是 DubboProtocol
,如果值是 register,那么获得的自适应扩展类就是 RegisterProtocol
。
那么我们来看下传入的参数是什么:
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
通过打断点,我们可以看出,当执行到上面那一行代码是,我们传入的参数如下图所示:
wrapperInvoker 里面包含了 invoker 对象,而 invoker 对象又包含了 url 对象,我们从 url 里面可以取出 protocol 的值为 register,因此我们最终获得的是RegisterProtocol
,所以我们就可以接着把断点打到 RegisterProtocol#export()
方法上。
当然,实际的过程更复杂,上面的
Protocol
接口还有对应的 Wrapper 包装类,这又涉及到 Dubbo SPI 的知识点,所以当调用PROTOCOL#export()
时,会产生如下的调用顺序:
- Protocol$Adaptive#export()
- ProtocolFilterWrapper#export()
- ProtocolListenerWrapper#export()
- RegistryProtocol#export()
这些之前关于 Dubbo SPI 的文章是有说明过的,这里再次提一下。
3.2 RegisterProtocol
紧接着上一步,我们来到了 RegisterProtocol#export()
方法,该方法很长,我们直接找到下面一行代码:
//export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
该方法的具体代码如下:
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
String key = getCacheKey(originInvoker);
return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
// 重点看这里 protocol.export(invokerDelegate)
return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
});
}
我们从上面提炼出核心步骤protocol.export(invokerDelegate)
, 从 3.1 小结可知,这里又会出现一条如下的调用链:
- Protocol$Adaptive
- ProtocolFilterWrapper
- ProtocolListenerWrapper
- DubboProtocol
3.3 DubboProtocol
到了这里,远程服务暴露流程先告一段落, openServer()
会去开启一个 NettyServer(这里默认通信框架是 Netty),去建立连接,接收消息。 这些内容后面的文章会详细讲解。
总结
本文讲解了远程服务暴露的大致的流程, 希望达成的目的如下:
- 了解整个大的流程框架会经过哪些核心环节,
- 解决整个流程执行过程的疑惑(或者说如何打断点),
比如上面提到的为什么会从 ServiceConfig
跳到 RegisterProtocol#export
方法, 归根到底还是基于 Dubbo SPI 机制, 同时也证明了 Dubbo SPI 是研究 Dubbo 原理的基石。