Dubbo2 巩固教程之四

大纲

前言

学习资源

Dubbo 深入理解

服务引用的源码剖析

版本说明

组件版本说明
Dubbo2.6.6在阅读 Dubbo 源码时要注意,不同版本由于协议升级、SPI 改动、注册中心增强等因素,底层实现会出现细微差异;版本跨度越大,差异通常越明显。

学习内容

  • 配置检查与加载
  • 处理直连(P2P)引用 URL
  • 多注册中心处理与 URL 组装
  • Invoker 的创建流程(核心类是 RegistryProtocol
    • (1) 连接注册中心
    • (2) 注册消费者节点
    • (3) 订阅提供者、配置覆盖、路由信息
    • (4) 处理多个服务提供者
    • (5) 本地缓存 Invoker 实例
    • (6) 返回代理工厂创建的服务代理对象

服务引用的流程

  • (1) 读取配置

    • Spring 启动 → 解析 @DubboReference / <dubbo:reference> → 生成 ReferenceBean。
  • (2) 初始化 ReferenceBean

    • 触发 afterPropertiesSet() → 准备接口名、协议、直连 URL、注册中心等信息。
  • (3) 发起引用

    • 调用 ReferenceConfig.get() → 开始构建 Invoker。
  • (4) 处理直连(可选)

    • 若配置了 url=xxx → 直接构造 Invoker,不走注册中心(Registry)。
  • (5) 注册中心引用

    • 若使用注册中心(Registry) → 调用 RegistryProtocol.refer()
      • 连接注册中心
      • 注册消费者的 URL
      • 消费者订阅 providers / routers / configurators 这三类节点
  • (6) 构建集群 Invoker

    • 多个 Provider → 创建多个 Invoker → 通过 Cluster(比如 FailoverCluster)合并为一个 ClusterInvoker。
  • (7) 创建代理对象

    • ProxyFactory.getProxy(ClusterInvoker) → 返回接口代理实例(比如,使用字节码操作技术 Javassist)。

服务消费者的配置

  • Dubbo 服务消费者的配置信息如下图所示:

  • Dubbo 服务消费者配置检查与加载的方法是:ReferenceConfig.init()

服务引用的入口方法

  • Dubbo 服务引用的入口方法是 ReferenceConfig.get(),可以从这个方法入手阅读 Dubbo 服务引用的底层源码实现
1
2
3
4
5
ReferenceConfig<OrderService> reference = new ReferenceConfig<OrderService>();

// 省略其他配置代码

reference.get(); // 引用服务

服务引用的 URL 组装

  • Dubbo 组装服务引用的 URL 的方法是:ReferenceConfig.createProxy(),核心代码如下图所示:

多注册中心引用服务

  • Dubbo 多注册中心引用服务的方法是:ReferenceConfig.createProxy(),核心代码如下图所示:

通过协议创建 Invoker

  • Dubbo 通过协议创建 Invoker 的方法是:ReferenceConfig.createProxy(),核心代码如下图所示:

提示

  • RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化。
  • 每当注册中心的内容发生变化后,RegistryDirectory 会动态创建或删除 Invoker,并通过 Router.route() 方法进行路由,过滤掉不符合路由规则的 Invoker。

通过 Invoker 构建接口代理实现

  • Dubbo 通过 Invoker 构建接口代理实现的方法是:ReferenceConfig.createProxy(),核心代码如下图所示:

初始化客户端(TCP)

  • 当使用的是 dubbo 协议时,Dubbo 获取 TCP 客户端的方法是:DubboProtocol.getClients(),核心代码如下图所示:

  • 当使用的是 dubbo 协议时,Dubbo 初始化 TCP 客户端的方法是:DubboProtocol.initClient(),核心代码如下图所示:

  • 当使用的是 dubbo 协议时,Dubbo 初始化 TCP 客户端的完整方法调用链是:DubboProtocol.getClients() -> DubboProtocol.initClient() -> Exchangers.connect() -> HeaderExchanger.connect() -> Transporters.connect() -> NettyTransporter.connect() -> NettyClient() -> AbstractClient() -> AbstractClient.doOpen() -> NettyClient.doOpen()

Dubbo 客户端建立 TCP 连接的时机

  • Dubbo Consumer 默认采用懒连接机制,Consumer 在引用服务时并不会主动建立与 Provider 的 TCP 连接;只有在 Consumer 第一次真正发起 RPC 调用时,Consumer 才会与 Provider 建立 TCP 连接。
  • 除非是显式禁用懒连接机制(如 <dubbo:reference lazy="false"> 或设置 <dubbo:reference init="true">),否则 Dubbo Consumer 默认不会在启动时主动连接 Provider,而是在第一次真正发起 RPC 调用时才建立 TCP 连接。

客户端的通信过程

Dubbo 客户端的通信步骤

Dubbo 客户端的 Invoker 执行

  • (1) 从接口代理实现的调用开始(服务引用的入口方法是 ReferenceConfig.get()

  • (2) 底层会执行 InvokerInvocationHandler.invoker() -> DubboInvoker.doInvoke(),这两个方法的核心源码如下图所示:

Dubbo 请求、响应处理器

  • ExchangeHandlerAdapter 的核心方法如下图所示:

Dubbo 编解码处理

ExchangeCodecDubboCodec 的核心方法如下图所示:

Dubbo 发送请求数据

  • Dubbo 发送请求数据的核心类和方法如下图所示:

Dubbo 服务端接收处理请求

Dubbo 服务端的线程派发

  • Dubbo 服务端默认支持以下几种线程派发策略(Dispatcher):
参数值含义说明特点与适用场景
all默认值,所有消息均派发到业务线程池最常见配置,网络 I/O 线程只负责读写,业务逻辑全部交由业务线程池处理。
direct所有消息直接在网络 I/O 线程上执行性能最高但风险大,可能会阻塞网络 I/O 线程,适合极轻量级、无阻塞逻辑的场景。
message只派发请求、响应消息到业务线程池连接、断开、心跳等消息仍在网络 I/O 线程中处理,适合请求耗时较长的场景。
execution仅派发请求消息到业务线程池,不包含响应消息响应和其它连接、断开、心跳消息,直接在网络 I/O 线程上执行,适合请求耗时较长的场景。
connection每个连接独立分配线程池为每个连接创建独立线程池,适合多连接、隔离性要求高的场景。
可扩展自定义派发策略(SPI 扩展)Dubbo 通过 SPI 机制支持扩展自定义 Dispatcher 实现,以满足特殊调度需求。

提示

Dispatcher 真实的职责是创建具有线程派发能力的 ChannelHandler,比如 AllChannelHandlerMessageOnlyChannelHandlerExecutionChannelHandler 等,其本身并不具备线程派发能力,详细介绍请看 这里

服务引用总结

Dubbo(2.7.x)服务引用的时序图(源自 Dubbo 官方文档)如下所示:

Dubbo (2.6.6)服务引用的完整调用链如下(仅供参考,并非绝对严谨):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ReferenceConfig.get()                                 ─► 服务引用入口
└─ ReferenceConfig.init() ─► 初始化 Reference 配置
└─ ReferenceConfig.createProxy(map) ─► 构建 Invoker + 创建代理
├─ 单个 URL:
│ └─ REF_PROTOCOL.refer(interfaceClass, url) ─► 协议引用
└─ 多个 URL:遍历每个 URL 执行 refer(),然后合并
├─ registryFactory.getRegistry(url) ─► 获取 Registry(如 Zookeeper)
├─ RegistryProtocol.refer() ─► 注册中心协议引用入口
│ └─ RegistryProtocol.doRefer()
│ ├─ registry.register(consumerUrl) ─► 注册消费者
│ ├─ registry.subscribe(directoryUrl) ─► 订阅服务变更
│ ├─ RegistryDirectory.subscribe() ─► 建立目录监听
│ └─ RegistryDirectory.notify() ─► 监听到 Provider 变更
│ └─ RegistryDirectory.toInvokers()
│ └─ protocol.refer(interface, providerUrl)
│ └─ DubboProtocol.refer() ─► Dubbo 协议引用
│ └─ DubboProtocol.initClient()
│ ├─ 获取或创建 ExchangeClient
│ └─ 初始化 NettyClient(连接)
└─ cluster.join(directory) ─► 合并 Directory → ClusterInvoker(如 FailoverClusterInvoker)
└─ ProxyFactory.getProxy(clusterInvoker) ─► 创建最终动态代理实例(比如,使用字节码操作技术 Javassist)