Skip to content
Charles Shao
Go back

Dubbo 深挖(开篇)· 架构全景、SPI 与一次调用全链路

views

RPC 开篇把 gRPC / Thrift / Dubbo 做了对照选型;广告 Java 微服务里,真正天天打交道的往往是 Apache Dubbo——注册、路由、负载均衡、容错、Filter 链都在 SDK 里开箱可用。这个系列专门深挖 Dubbo:从架构与 SPI,到注册发现、Cluster 治理、协议序列化,再到流量治理与优雅上下线,一路钻到能排查线上调用、能设计扩展点的深度。

开篇这一篇先把地基铺平:Dubbo 的三角角色与分层模型长什么样,SPI 怎么把框架「拆成可插拔零件」,一次 budgetService.deduct(...) 从代理到字节流到底走了哪些层。

本文是 Dubbo 深挖系列的第 1 篇(开篇 · 架构全景、SPI 与一次调用全链路)。 全系列 5 篇:

  1. Dubbo 深挖(开篇)· 架构全景、SPI 与一次调用全链路(本篇)
  2. Dubbo 深挖 · 注册中心、服务发现与元数据
  3. Dubbo 深挖 · Cluster 容错、负载均衡与路由
  4. Dubbo 深挖 · 协议栈与 Triple / 序列化
  5. Dubbo 深挖 · 流量治理、Filter 链与优雅上下线

一句话定位:Dubbo = 以 URL 为配置载体、以 SPI 为扩展骨架、以 Cluster 为治理中枢的 Java RPC 框架;本篇讲清三角角色、分层栈与一次调用的完整路径,是读懂后面四篇的地基。

TL;DR

Table of contents

Open Table of contents

1. 为什么广告 Java 栈常落到 Dubbo

广告实时链路拆成「定向 → 出价 → 预算 → 频次」多跳之后,进程间通信要同时满足:

  1. 低延迟:单跳超时常常只有几十毫秒预算;
  2. 可治理:按机房 / 单元 / 版本灰度、权重、熔断要能动态改;
  3. 可观测:一次调用要能挂上 Trace、Metrics、访问日志;
  4. Java 友好:接口即契约、Spring 集成、热配置。

gRPC 在跨语言与云原生生态上更「默认」;但在以 Java 为主、要开箱 Cluster 治理的团队里,Dubbo 仍是高频选择——Directory / Router / LoadBalance / Failover 不用从零拼。RPC 开篇 §3 讲过选型对照;本系列默认你已经选了 Dubbo,接下来钻机制。

Dubbo ≠「又一个 HTTP Client」。它把「找人、挑人、容错、编解码、横切」收成一套可插拔栈;业务只看见接口方法。

2. 三角角色与部署全景

Dubbo 三角角色与部署全景图。左侧多个 Consumer(竞价、计费等)通过 Registry(Nacos/ZooKeeper)订阅服务地址;中间 Registry 存 Provider 列表与元数据变更;右侧多个 Provider(预算、定向、频次)向 Registry 注册。Consumer 与 Provider 之间是直连 RPC(长连接),Registry 只负责地址推送不在调用热路径上。底部旁注:配置中心管动态参数,元数据中心存接口定义,与地址发现解耦。

2.1 Provider(服务提供方)

暴露接口实现,向注册中心注册 ip:port + 服务标识(接口名、group、version)及元数据(权重、超时默认值、应用名等)。进程启动完成、端口就绪后再注册——顺序反了就会出现「端口未开就被打」的短暂故障。

2.2 Consumer(服务消费方)

订阅感兴趣的服务,拿到 Provider 地址列表后在进程内做负载均衡与容错,直连 Provider(经典客户端发现模型,见服务注册与发现)。注册中心宕机时,只要本地缓存的地址还在,存量调用通常仍可继续——这也是为什么「推空保护」和缓存过期策略如此重要(下一篇展开)。

2.3 Registry / 配置 / 元数据

早期 ZooKeeper 往往一身多职:地址 + 配置 + 少量元数据。Dubbo 2.7+ 趋势是:

组件职责典型实现
注册中心实例地址、上下线事件Nacos / ZooKeeper / Redis…
配置中心动态超时、路由规则、开关Nacos / Apollo…
元数据中心接口方法签名、参数类型等大块元数据Nacos / Redis / 本地…

把「大块接口描述」从地址推送里拆出去,是为了缩小注册中心通知体积、加快推送——广告集群几千实例滚动时,这点差别会被放大。

3. 分层架构:从业务接口到字节流

Dubbo 分层架构示意图。自上而下:业务接口层 → Proxy 层(动态代理)→ Cluster 层(Directory / Router / LoadBalance / Cluster 容错)→ Protocol 层(Dubbo / Triple)→ Exchange / Transport(Request-Response · Netty)。右侧标注每层对应的扩展点与后续篇章。

自上而下读:

干什么本系列落点
业务 / API接口与实现、Spring 装配本篇
Proxy把远程调用伪装成本地方法本篇 §5
Cluster路由、负载均衡、容错、粘滞第 3 篇
Protocol协议帧、序列化协商、Invoker 暴露/引用第 4 篇
Transport连接、心跳、线程模型(Netty)第 4 篇 + Netty 系列
Filter横切:鉴权、限流、Trace、日志第 5 篇

心智模型:Proxy 骗过直觉,Cluster 决定打谁、挂了怎么办,Protocol/Transport 决定字节怎么走,Filter 挂横切。 排障时先问「卡在哪一层」。

4. URL 模型:配置怎么在全栈流转

Dubbo 几乎所有决策都围绕 URL(更准确说是内部的 URL 对象):

dubbo://10.0.1.12:20880/com.ad.BudgetService
  ?version=1.0.0
  &group=bidding
  &timeout=80
  &retries=0
  &loadbalance=leastactive
  &serialization=hessian2
  &application=bidder-budget

要点:

实践含义:线上「这个接口超时怎么是 1000ms」——去看 Consumer 侧 URL 合并结果(本地配置 × 远端覆盖 × 方法级覆盖),不要只看 YAML 里写了什么。配置合并优先级细节在流量治理篇还会碰到。

5. SPI:框架为什么「处处可插拔」

5.1 比 JDK SPI 多了什么

JDK ServiceLoader 能加载实现,但缺少:按名获取、按 URL 自适应选择、按条件自动激活、IOC/AOP 包装。Dubbo SPI 补齐了这些,于是 Protocol、LoadBalance、Filter、Serialization、Registry 都是扩展点。

典型文件:

META-INF/dubbo/org.apache.dubbo.rpc.Protocol
  dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
  tri=org.apache.dubbo.rpc.protocol.tri.TripleProtocol

5.2 三个注解心智

机制作用
@SPI("defaultName")标记扩展接口,并给默认实现名
@Adaptive生成自适应代理:运行时按 URL 参数选真正实现
@Activate(group=..., order=..., value=...)Filter 等「自动挂上」的条件与顺序

5.3 扩展点纪律(生产血泪)

SPI 让平台能统一加「单元路由 / 压测染色 / 鉴权」,也能让业务变成 Filter 动物园——每条业务线挂私货,最后谁也说不清一次调用过了哪些层。

纪律建议:

  1. 平台统一扩展,业务默认零私有 Filter;
  2. 新增扩展必须带 order 与文档,并进调用链可视化;
  3. 热路径扩展禁止重 IO、禁止无界阻塞。

6. 一次调用全链路

一次 Dubbo 调用全链路时序图。Consumer 侧:业务代码 → Proxy → Consumer Filter 链 → Cluster.invoke → Directory 列出 Invoker → Router 过滤 → LoadBalance 选择 → Protocol refer 出的 Invoker → 编解码发送。网络到达 Provider:解码 → Provider Filter 链 → 业务实现 → 编码响应返回。

逐步对齐源码心智(名字随版本略有差异,骨架稳定):

  1. 业务调用接口方法;
  2. Proxy(Javassist / JDK 动态代理)进入 InvokerInvocationHandler
  3. Consumer Filter 链(Trace、超时附件、鉴权等);
  4. Cluster Invoker:从 Directory 取 Invoker 列表 → Router 过滤 → LoadBalance 选一个 → 按 Cluster 策略调用(Failover / Failfast…);
  5. Protocol Invoker 把 Invocation 交给编解码与传输;
  6. Provider 解码 → Provider Filter → 实现类方法 → 响应回写。

排障对照:

现象优先怀疑
地址列表为空 / No provider注册发现、订阅、分组版本不一致
总打到同一台 / 不均LoadBalance、权重、路由规则
超时尖刺集中在发布窗口优雅下线顺序、缓存延迟
偶发重试放大流量retries + 非幂等写
序列化异常接口演进、类缺失、序列化协议不一致

7. 调用语义:同步、异步、泛化

广告侧经验:写路径(扣预算)默认同步 + 幂等 + retries=0 或极短预算读路径扇出才优先异步。无脑给写接口开 Failover 重试,是RPC 开篇里那次雪崩的根源。

8. 最小可运行心智(Spring Boot 素描)

不贴完整可复制工程,只钉住「两边要对齐的身份」:

Provider

@DubboService(version = "1.0.0", group = "bidding", timeout = 80)
public class BudgetServiceImpl implements BudgetService {
  public DeductResult deduct(DeductRequest req) { /* ... */ }
}

Consumer

@DubboReference(version = "1.0.0", group = "bidding", timeout = 80, retries = 0)
private BudgetService budgetService;

对齐清单:

9. 生产反模式(开篇清单)

10. 常见误解 ↔ 正解

常见误解正解
Dubbo 调用一定走注册中心热路径是 Consumer↔Provider 直连;注册中心只推地址
配了 loadbalance 就一定均匀还受路由、权重、活跃数、粘滞影响
超时配在 Provider 就够了真正卡住调用方的是 Consumer 超时(及 deadline 透传)
SPI 扩展越多越强热路径每多一环都是延迟与故障面
Triple 只是换皮 HTTP协议、序列化、流式、互通模型都变了,见第 4 篇

11. 速查表

下一篇进入发现层——注册中心、服务发现与元数据:地址从哪来、推空怎么防、元数据中心为什么要拆出去,以及滚动发布时如何避免「僵尸实例仍接流量」。


延伸阅读

本系列内部串读(Dubbo 深挖,本篇是地基):

相关系列:

一手资料:


views
Share this post on:

Previous Post
Dubbo 深挖 · 注册中心、服务发现与元数据
Next Post
API 网关与分布式追踪