Skip to content

Dubbo ​

Java 高级 · 阿里中间件

Dubbo 是高性能 Java RPC 框架。会用 @DubboService/@DubboReference 只是入门,真正的功力在于读懂:一次 RPC 调用背后 Proxy/Cluster/LoadBalance/Filter/Invoker 如何协作?Dubbo 的 SPI 为什么比 JDK SPI 强?Netty 传输与心跳怎么保活? 本文拆解调用链、自适应扩展、集群容错与优雅下线。

一、架构角色与一次调用链路 ​

  • Provider:服务提供者,暴露接口。
  • Consumer:服务消费者,引用接口。
  • Registry:注册中心(Nacos/ZK),Provider 注册、Consumer 订阅。
  • Monitor:监控中心(可选),统计调用次数/耗时。
  • Container:服务运行容器。

一次调用的完整协作(Consumer 侧):

二、SPI:自适应扩展机制 ​

Dubbo 的微内核架构建立在自研 SPI(ExtensionLoader)之上,比 JDK 标准 SPI 强在三处:

  1. 按需实例化 + IoC:JDK SPI 一次性实例化所有实现;Dubbo SPI 只加载,用到才 getExtension(name) 实例化,并支持 setter 注入依赖。
  2. @Adaptive 自适应扩展:运行时根据 URL 参数动态选实现。编译期生成一个适配器类,方法里从 URL 取 key,再 getExtension 实际实现。这是 Dubbo "URL 驱动一切"的核心。
  3. Wrapper 自动包装(AOP):带构造器参数是该接口的 Wrapper 会被自动层层包裹,形成装饰链(如 ProtocolFilterWrapper 把 Filter 链织入)。
java
// 接口声明扩展点
@SPI("dubbo")  // 默认实现名
public interface Protocol {
    @Adaptive   // 自适应: 从 URL 的 protocol 参数决定用哪个实现
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
}
// META-INF/dubbo/org.apache.dubbo.rpc.Protocol 内容:
// dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
// grpc=org.apache.dubbo.rpc.protocol.grpc.GrpcProtocol

TIP

为什么 Dubbo 用 URL 贯穿全链路?因为 RPC 的所有参数(协议、序列化、超时、版本、分组、负载均衡策略)都被编码进一个 URL 对象,传给每一层。这样 Cluster、LoadBalance、Protocol 都能从同一个 URL 里读出自己该用什么实现——这就是 @Adaptive 能"运行时动态选实现"的前提。理解 URL 即理解 Dubbo 的扩展哲学。

三、Invoker:调用链的通用抽象 ​

Dubbo 把"可调用的实体"统一抽象为 Invoker:它持有"invoker 是谁(URL)、能调什么(interface)、怎么调(invoke)"。无论是本地 Invoker、远程 Invoker、还是 Cluster 聚合后的 Invoker,对上层都是同一个接口。

  • Consumer 侧:@DubboReference 通过 ProxyFactory.getProxy 用 Javassist/CGLIB 动态代理生成 Proxy,把接口方法调用转成 Invoker.invoke(invocation)。多个 Provider 被 Cluster 聚合成一个 Cluster Invoker。
  • Filter 链:ConsumerContextFilter/ActiveLimitFilter 等在 Invoker 外层包裹,做 traceId 透传、激活数限制等。
  • Provider 侧:收到的请求经 Protocol 解码 → 找到 Invoker → 经 Filter 链 → 反射调用真实实现类。

四、集群容错与负载均衡 ​

Cluster 把多个 Provider Invoker 聚合成一个,负责容错:

容错策略行为场景
Failover(默认)失败重试其他节点(retries 次)读操作
Failfast失败立即抛非幂等写
Failsafe失败忽略日志等不重要的
Forking并行调多个,一个成功即返回低延迟敏感
Broadcast广播所有节点通知类

负载均衡:Random(加权随机,默认)、RoundRobin(加权轮询,带平滑)、LeastActive(最少活跃调用,谁闲给谁)、ConsistentHash(一致性哈希,相同 key 落同节点,保会话粘性与缓存命中)。

java
@DubboService(version = "1.0.0", timeout = 3000)
public class UserServiceImpl implements UserService { ... }

@DubboReference(version = "1.0.0",
    loadbalance = "consistenthash",   // 同 key 打到同一 Provider
    cluster = "failover", retries = 2,
    timeout = 3000)
private UserService userService;

DANGER

  • 非幂等接口用了 Failover 重试:扣款、下单等写操作,重试会执行多次(重复扣款)。非幂等接口必须 retries=0 或加幂等键(如请求携带 idempotentKey,落幂等表去重)。
  • 超时设置不合理:Consumer 超时过短 + Failover 会放大故障(一个慢节点触发多次重试,雪崩);Provider 处理慢应先优化而非一味加 timeout。
  • 接口版本不兼容就上线:version 不变导致旧 Consumer 调到新 Provider 字段缺失。重大变更必须升 version + 灰度。
  • 大对象跨网络:DTO 含巨型集合撑爆网络与序列化。应裁剪字段、分页。

五、Netty 传输与心跳 ​

Dubbo 默认用 Netty 做传输(4.x NIO 主从模型)。DubboProtocol 在 Provider 侧起 Netty Server 监听端口,Consumer 侧用 NettyClient 连接。关键机制:

  • 编解码:请求/响应用 ExchangeCodec 编码成 Dubbo 私有协议(魔数 + 头 + body),body 用 Hessian2/Kryo 序列化。请求头带 requestId 关联异步响应(Netty IO 线程解码后,按 requestId 唤醒等待的 Future)。
  • 心跳:Consumer/Provider 间定时发心跳包(默认 heartbeat=60s),对方在 heartbeatTimeout 内无响应则断连并触发重连/剔除,避免半开连接。
  • 异步化:调用默认返回 CompletableFuture,NettyClient 发完不阻塞,等响应回来按 requestId 完成 Future。

六、注册中心交互与优雅下线 ​

  • 注册/订阅:Provider 启动 → 向 Registry 注册(含接口、IP、端口、权重、版本);Consumer 启动 → 订阅,Registry 把可用列表推给 Consumer,NotifyListener 更新本地 Directory(Invoker 目录)。
  • 优雅下线(graceful shutdown):服务收到 SIGTERM 时,Dubbo 先注销注册中心上的实例(让 Consumer 不再路由新请求),等待已在进行中的调用处理完(默认 wait=10000ms),再关闭协议端口。这避免了"直接 kill 导致正在处理的请求失败"。
xml
<dubbo:service interface="com.demo.UserService" ref="userService"
    version="1.0.0" timeout="3000" retries="2"/>
<dubbo:reference id="userService" interface="com.demo.UserService"
    version="1.0.0" loadbalance="leastactive" cluster="failover"/>

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 画出 Consumer 到 Provider 一次 RPC 中 Proxy/Cluster/LoadBalance/Filter/Invoker 的协作关系,标注 Netty 在哪一层。
  2. 设计"非幂等扣款接口"如何避免 Failover 重试造成重复扣款(幂等键 + retries=0 + 幂等表)。
  3. 用 @Adaptive 思想说明 Dubbo SPI 相比 JDK SPI 的扩展优势(按需实例化 / URL 自适应 / Wrapper AOP)。
  4. 阐述 Dubbo 优雅下线的步骤,为什么不能直接 kill -9?

本节小结

Dubbo 把"服务注册发现 + 动态代理(Javassist)+ Cluster 容错 + 自适应 SPI + Netty 传输"封装为成熟体系。SPI(URL 驱动 + @Adaptive 运行时选实现)是微内核的根,Invoker 是统一调用抽象,Cluster+LoadBalance 做容错与选路,Netty 做高效传输与心跳,优雅下线避免请求丢失。用好它的关键:版本治理、合理超时、幂等保护、可观测。