Dubbo源码分析

设计

分层架构

Dubbo 最显著的特点就是其分层架构。官方文档中将其划分为 10 层,但为了便于理解,我们可以将其归纳为三大块:业务层、RPC 核心层、Remoting 通信层

业务层 (Business Layer):

  • Service 层: 开发者定义的业务接口和实现。
  • Config 层: 对外配置层,用于配置服务的暴露和引用,如 ServiceConfigReferenceConfig。这是开发者直接接触的部分。

RPC 核心层 (RPC Core Layer): 这是 Dubbo 功能的核心所在。

  • Proxy 层: 服务代理层。为消费者端生成接口的代理对象,使其能像调用本地方法一样调用远程服务。
  • Registry 层: 注册中心层。负责服务的注册与发现,封装了与 Zookeeper、Nacos 等注册中心的交互。
  • Cluster 层: 集群容错层。当从注册中心获取到多个服务提供者时,由该层决定如何选择(负载均衡)以及在调用失败时如何应对(容错机制)。
  • Monitor 层: 监控层。负责统计服务的调用次数、耗时等信息。
  • Protocol 层: 远程调用层。这是 RPC 的核心,封装了调用协议的细节,如 DubboProtocol, HttpProtocol, TripleProtocol 等。它负责管理 Invoker 的生命周期。

Remoting 通信层 (Remoting Layer):

  • Exchange 层: 信息交换层。在 Transport 层之上封装了请求-响应模型。
  • Transport 层: 网络传输层。抽象了 ClientServer,封装了底层网络库(如 Netty, Mina)的细节。
  • Serialize 层: 序列化层。负责对象与字节流之间的转换。

核心机制

微内核 + SPI 扩展机制

微内核架构 (Microkernel Architecture) 与 开闭原则 (Open/Closed Principle) Dubbo 的核心(Kernel)非常小,它本身只负责整合和流程串联。所有具体的功能,如协议、序列化、负载均衡、注册中心等,都被抽象为接口,并通过 SPI 机制作为可插拔的扩展点 (Extension Point) 来加载。

SPI实现:

核心类: org.apache.dubbo.common.extension.ExtensionLoader 这个类是 Dubbo SPI 机制的心脏。每个扩展点接口(如 Protocol, LoadBalance)都有一个与之对应的 ExtensionLoader 实例,负责该扩展点的所有实现类的加载、缓存和管理。

约定大于配置:

  1. @SPI 注解: 在一个接口上标注 @SPI,即表示该接口是一个扩展点。注解的 value 是该扩展点的默认实现名称。

    Java

    1
    2
    @SPI("dubbo") // 默认使用 DubboProtocol
    public interface Protocol { ... }
  2. 配置文件: 所有的扩展实现都定义在 META-INF/dubbo/ 目录下的以接口全限定名命名的文件中。 文件内容是 key=value 的形式,key 是扩展名,value 是实现类的全限定名。 例如,META-INF/dubbo/org.apache.dubbo.rpc.Protocol 文件内容可能为:

    Properties

    1
    2
    3
    dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
    http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
    triple=org.apache.dubbo.rpc.protocol.tri.TripleProtocol

强大的特性:

  • 自动注入 (IOC): ExtensionLoader 会自动为加载的扩展实例注入其依赖的其他扩展点。
  • 自适应扩展 (Adaptive Extension): Dubbo 中最精妙的设计之一。它能为扩展点接口动态生成一个代理类,这个代理类在运行时能根据调用上下文(通常是 URL 对象)来动态决定具体使用哪个扩展实现。这使得 Dubbo 可以在运行时灵活切换行为。
  • 自动包装 (AOP): 如果一个扩展实现类有拷贝构造函数,Dubbo 会认为它是一个 Wrapper 类,用它来包装其他扩展实现,从而轻松实现 AOP 功能。

RPC的生命周期

服务提供者 (Provider) 暴露过程

触发: 当 Spring 容器启动时,解析到 <dubbo:service>@DubboService

配置解析: ServiceConfig 类承载了所有配置信息。其 export() 方法是服务暴露的入口。

协议选择与暴露: export() 方法会调用 doExportUrls(),它会根据配置的协议(如 dubbo),通过 SPI 机制获取 Protocol 接口的对应实现,即 ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo"),得到 DubboProtocol 实例。

打开服务器: DubboProtocol.export() 方法会启动一个网络服务器(如 NettyServer,这又是通过 SPI 获取的 Transporter 扩展)。同时,它会将业务实现类包装成一个 Invoker 对象,并维护一个 ip:port -> Invoker 的映射。

服务注册: Protocol 的实现类(通常是 RegistryProtocol,它包装了真正的协议实现)会构建服务提供者的 URL,然后通过 SPI 获取 Registry 扩展(如 ZookeeperRegistry),调用其 register() 方法,将服务信息注册到注册中心。

服务消费者 (Consumer) 引用与调用过程

触发: Spring 容器启动,解析到 <dubbo:reference>@DubboReference

配置解析: ReferenceConfig 类承载配置,其 get() 方法是获取远程服务代理的入口。

创建代理: get() 方法的核心是创建代理。它同样通过 RegistryProtocol 从注册中心订阅服务提供者列表。

集群与容错: RegistryProtocol 将获取到的多个提供者 URL 列表,通过 SPI 获取 Cluster 扩展(如 FailoverCluster),调用其 join() 方法。Cluster 会将多个 Invoker 聚合成一个单一的、具备容错能力的 Invoker

生成代理: ProxyFactory 扩展(如 JavassistProxyFactory)会为业务接口生成一个代理对象。这个代理对象内部持有了上一步生成的 Cluster Invoker

发起调用: 当开发者调用代理对象的方法时:

a. 请求会进入代理对象的 InvocationHandler,最终会调用 Cluster Invokerinvoke() 方法。

b. Cluster Invoker 内部会通过 SPI 获取 LoadBalance 扩展(如 RandomLoadBalance)从多个提供者 Invoker 中选择一个。

c. 选中的 Invoker(代表一个远程连接)会通过 ExchangeClient -> NettyClient 发送请求。

d. 请求数据会经过 Serialization 扩展(如 Hessian2Serialization)进行序列化。

e. 字节流通过 Netty 发送到服务提供者端。

接收与执行: 提供者端的 NettyServer 接收到数据,经过反序列化后,找到对应的 Invoker,最终调用到业务实现类的原始方法。执行结果再原路返回。

精髓:

统一模型 URL: Dubbo 设计中另一个极其 brilliant 的地方,就是使用 URL 对象作为其核心配置的总线。无论是服务提供者、消费者、注册中心信息,还是调用过程中的各种参数,都被统一封装在 URL 对象中,在各层之间传递。这使得整个框架几乎是无状态的,极大地简化了扩展的实现难度,因为所有扩展都可以从 URL 中获取自己需要的所有上下文信息。