Dubbo 专栏
Dubbo 是 Java 生态里非常典型的高性能 RPC 框架。它解决的核心问题不是“怎么发 HTTP 请求”,而是:服务拆成多个进程以后,调用方怎样像调用本地接口一样调用远程服务,同时还能做到注册发现、负载均衡、超时、重试、容错、治理、监控和扩展。
学习 Dubbo 不能只背 @DubboService、@DubboReference。真正要理解的是:
- 为什么微服务需要 RPC。
- 一次 Dubbo 调用从代理对象到网络传输再到服务端反射执行经历了什么。
- 注册中心在 Dubbo 里保存什么,不负责什么。
- 协议、序列化、连接、线程池、负载均衡、集群容错分别解决什么问题。
- Dubbo SPI 为什么是核心扩展机制。
- 商业项目中哪些场景适合 Dubbo,哪些场景更适合 HTTP、MQ 或事件驱动。
学习目标
学完本专栏后,你应该能做到:
| 能力 | 具体要求 |
|---|---|
| 会用 | 能写 provider、consumer、接口包和注册中心配置 |
| 懂链路 | 能画出一次远程调用的完整流程 |
| 懂原理 | 能解释代理、Invoker、Directory、Router、LoadBalance、Cluster、Protocol、Codec、Transport |
| 会治理 | 能配置超时、重试、负载均衡、灰度、版本、分组、降级 |
| 会排查 | 能定位注册失败、找不到服务、超时、线程池打满、序列化失败、重复调用 |
| 会面试 | 能用标准回答讲清 Dubbo 和 Spring Cloud OpenFeign 的区别 |
本专栏学习路径
| 阶段 | 文档 | 学习重点 |
|---|---|---|
| 入门 | RPC 与 Dubbo 总览 | 先理解 Dubbo 解决什么问题 |
| 核心 | 调用链路与核心架构 | 掌握代理、Invoker、Filter、Protocol、Exchange、Transport |
| 注册 | 注册发现、Directory刷新与故障恢复 | 理解接口级/应用级发现、注册订阅、元数据、地址对账和Session窗口 |
| 协议 | 协议、序列化与线程模型 | 理解 Dubbo 协议、Hessian2、连接复用、线程池 |
| 治理 | 路由、负载均衡、Cluster与重试预算 | 理解逻辑调用与物理尝试、算法、超时预算、灰度和Provider保护 |
| 扩展 | Dubbo SPI 与扩展点 | 理解 Dubbo 为什么能高度可插拔 |
| 面试 | Dubbo 面试题 | 标准回答、追问点、项目话术和跳转 |
为什么需要 RPC
单体应用里,订单模块调用库存模块通常就是一个普通方法:
stockService.deduct(skuId, count);拆成微服务后,订单服务和库存服务可能在不同机器、不同 JVM 进程中。此时本地方法调用不再成立,必须通过网络调用:
flowchart TD
A["订单服务进程"] --> B["网络"]
B --> C["库存服务进程"]
C --> D["库存数据库"]如果每个服务都手写 Socket、序列化、超时、连接池、负载均衡、重试、服务发现,业务代码会非常混乱。RPC 框架的价值,就是把这些通用能力封装起来,让业务开发面向接口编程。
RPC 和 HTTP 调用有什么区别
RPC 不是一定比 HTTP 高级,它们解决的问题有重叠,也有差异。
| 对比项 | Dubbo RPC | Spring Cloud OpenFeign HTTP |
|---|---|---|
| 调用体验 | 像本地 Java 接口调用 | 像声明式 HTTP 客户端 |
| 协议 | Dubbo 协议、Triple、REST 等 | HTTP/1.1、HTTP/2 |
| 序列化 | Hessian2、Fastjson2、Protobuf 等 | JSON 最常见 |
| 性能 | 二进制协议通常更轻 | JSON 可读性强但体积较大 |
| 契约 | Java 接口契约强 | HTTP API 契约更开放 |
| 跨语言 | 传统 Dubbo Java 生态强,Triple 更利于跨语言 | 跨语言天然友好 |
| 治理 | 服务治理能力非常丰富 | 依赖 Spring Cloud 组件组合 |
| 适合场景 | Java 内部服务、高性能强契约调用 | 开放 API、前后端、跨语言、云原生 HTTP 主线 |
商业项目里常见组合是:外部入口用 HTTP/Gateway,内部核心 Java 服务之间用 Dubbo;跨系统异步解耦用 MQ。
一次 Dubbo 调用的简化流程
flowchart TD
A["Consumer 注入接口代理"] --> B["调用接口方法"]
B --> C["代理转换成 Invocation"]
C --> D["从注册中心获取 Provider 地址"]
D --> E["路由过滤不可用实例"]
E --> F["负载均衡选择一个实例"]
F --> G["编码和序列化请求"]
G --> H["Netty 发送网络请求"]
H --> I["Provider 解码请求"]
I --> J["Filter 链处理"]
J --> K["反射调用真实服务方法"]
K --> L["序列化响应返回 Consumer"]这张图说明两件事:
- Dubbo 的“像本地调用”只是编程体验,底层仍然是网络调用。
- 网络调用一定会出现超时、失败、重复、半成功,所以业务仍然要设计幂等、超时、降级和补偿。
最小 Demo:订单服务调用库存服务
项目通常拆成三个模块:
| 模块 | 作用 |
|---|---|
stock-api | 放公共接口和 DTO,provider 与 consumer 都依赖 |
stock-provider | 实现库存服务并暴露 Dubbo 服务 |
order-consumer | 引用库存服务并发起远程调用 |
公共接口
package com.example.stock.api;
public interface StockService {
boolean deduct(String skuId, int count);
}Provider 实现
package com.example.stock.provider;
import com.example.stock.api.StockService;
import org.apache.dubbo.config.annotation.DubboService;
@DubboService(version = "1.0.0", group = "trade")
public class StockServiceImpl implements StockService {
@Override
public boolean deduct(String skuId, int count) {
if (count <= 0) {
throw new IllegalArgumentException("count must be positive");
}
System.out.println("扣减库存 skuId=" + skuId + ", count=" + count);
return true;
}
}Provider 配置
server:
port: 8081
dubbo:
application:
name: stock-provider
protocol:
name: dubbo
port: 20880
registry:
address: zookeeper://127.0.0.1:2181
scan:
base-packages: com.example.stock.providerConsumer 引用
package com.example.order;
import com.example.stock.api.StockService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@DubboReference(
version = "1.0.0",
group = "trade",
timeout = 2000,
retries = 0,
loadbalance = "roundrobin"
)
private StockService stockService;
@PostMapping("/orders")
public String createOrder(@RequestParam String skuId, @RequestParam int count) {
boolean success = stockService.deduct(skuId, count);
return success ? "order created" : "stock not enough";
}
}Consumer 配置
server:
port: 8082
dubbo:
application:
name: order-consumer
registry:
address: zookeeper://127.0.0.1:2181这个 demo 里最重要的不是注解,而是配置背后的含义:
| 配置 | 含义 | 不配置或配错会怎样 |
|---|---|---|
registry.address | 注册中心地址 | provider 注册不上,consumer 找不到服务 |
protocol.port | provider 暴露 RPC 端口 | 端口冲突或防火墙不通会调用失败 |
version | 服务版本 | 多版本共存时避免调用错实现 |
group | 服务分组 | 多业务线或多环境隔离 |
timeout | 远程调用超时 | 不设置可能长时间阻塞调用线程 |
retries | 失败重试次数 | 写接口重试可能造成重复扣减 |
商业常用场景
| 场景 | Dubbo 适合点 | 必须注意 |
|---|---|---|
| 订单调用库存 | Java 内部服务强契约调用 | 扣库存接口要幂等,写操作慎用重试 |
| 资产平台调用字典服务 | 低延迟、接口稳定 | 字典可缓存,避免每次远程调用 |
| 医疗采集服务调用规则服务 | 接口治理、版本控制 | 规则版本要隔离,灰度发布 |
| 权限服务被多个系统调用 | 公共能力复用 | 权限结果缓存和降级策略 |
| 批处理调用基础服务 | 高吞吐内部调用 | 控制并发,避免打满 provider 线程池 |
初学者必须建立的正确认知
| 误区 | 为什么不对 | 正确理解 |
|---|---|---|
| Dubbo 调用就是本地调用 | 底层经过网络,可能失败和超时 | 编程像本地,运行时是远程 |
| 有注册中心就不会调用失败 | 注册中心只管地址发现 | 调用还受网络、线程池、序列化、超时影响 |
| 重试越多越可靠 | 写接口重复执行会造成脏数据 | 查询可重试,写接口优先幂等和补偿 |
| Dubbo 一定比 HTTP 好 | 场景不同 | 内部 Java 强契约适合 Dubbo,开放和跨语言适合 HTTP |
| Provider 机器多就一定稳定 | 慢实例也会拖垮调用方 | 需要超时、熔断、限流、隔离和监控 |
和其他知识点的关系
- Java 网络编程:理解 TCP、连接、超时。
- Java 序列化:理解对象如何变成字节。
- Netty:Dubbo 默认网络通信常依赖 Netty。
- ZooKeeper:理解 Dubbo 传统注册中心和协调能力。
- 幂等设计:远程写接口必须考虑重复调用。
- 分布式限流:保护 Provider 和核心下游。
- Spring Cloud Feign:对比 HTTP 声明式调用。
面试标准回答
Dubbo 是 Java 生态常用的高性能 RPC 框架。它通过接口代理屏蔽远程调用细节,Consumer 调用接口时会把方法、参数、附件等封装成 Invocation,再经过集群容错、路由、负载均衡选择 Provider,之后通过协议编码、序列化和网络通信发送到服务端。Provider 解码后经过 Filter 链,最终调用真实服务实现并返回结果。Dubbo 还提供注册发现、版本分组、超时重试、负载均衡、熔断降级、SPI 扩展等服务治理能力。
追问时要补一句:Dubbo 的本地调用体验不代表它是本地调用,底层仍然是不可靠网络,所以生产上必须设置超时、控制重试、设计幂等、做监控告警和降级。
本章小结
Dubbo 的核心不是几个注解,而是一套完整的 RPC 调用和服务治理体系。学 Dubbo 要从远程调用的不可靠性出发,理解代理、注册发现、路由、负载均衡、协议、序列化、线程模型、容错和扩展点。只有这样,线上遇到“找不到服务、调用超时、重复扣减、线程池打满、序列化失败”时,才知道该从哪条链路排查。
