天天看点

Dubbo 源码分析 - Dubbo 远程服务暴露流程(一)

前言

前面用了 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

的方法)。

Dubbo 源码分析 - Dubbo 远程服务暴露流程(一)

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

通过打断点,我们可以看出,当执行到上面那一行代码是,我们传入的参数如下图所示:

Dubbo 源码分析 - Dubbo 远程服务暴露流程(一)

wrapperInvoker 里面包含了 invoker 对象,而 invoker 对象又包含了 url 对象,我们从 url 里面可以取出 protocol 的值为 register,因此我们最终获得的是

RegisterProtocol

,所以我们就可以接着把断点打到

RegisterProtocol#export()

方法上。

当然,实际的过程更复杂,上面的

Protocol

接口还有对应的 Wrapper 包装类,这又涉及到 Dubbo SPI 的知识点,所以当调用

PROTOCOL#export()

时,会产生如下的调用顺序:
  1. Protocol$Adaptive#export()
  2. ProtocolFilterWrapper#export()
  3. ProtocolListenerWrapper#export()
  4. 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 小结可知,这里又会出现一条如下的调用链:

  1. Protocol$Adaptive
  2. ProtocolFilterWrapper
  3. ProtocolListenerWrapper
  4. DubboProtocol

3.3 DubboProtocol

到了这里,远程服务暴露流程先告一段落,

openServer()

会去开启一个 NettyServer(这里默认通信框架是 Netty),去建立连接,接收消息。 这些内容后面的文章会详细讲解。

总结

本文讲解了远程服务暴露的大致的流程, 希望达成的目的如下:

  1. 了解整个大的流程框架会经过哪些核心环节,
  2. 解决整个流程执行过程的疑惑(或者说如何打断点),

比如上面提到的为什么会从

ServiceConfig

跳到

RegisterProtocol#export

方法, 归根到底还是基于 Dubbo SPI 机制, 同时也证明了 Dubbo SPI 是研究 Dubbo 原理的基石。