Dubbo 2 巩固教程之一
大纲
- Dubbo 2 巩固教程之一、Dubbo 2 巩固教程之二、Dubbo 2 巩固教程之三
- Dubbo 2 巩固教程之四、Dubbo 2 巩固教程之五、Dubbo 2 巩固教程之六
- Dubbo 2 巩固教程之七
前言
学习资源
Dubbo 核心特性
通信协议
特别注意
- Dubbo 支持多种通信协议,在
3.0版本之前支持 Dubbo、gRPC、Hessian2、REST 等核心通信协议。 - 从 Dubbo
3.0开始,Dubbo 官方新引入了自研的 Triple 通信协议,不过也保留了 Dubbo 和 REST 通信协议的支持。 - 从 Dubbo
3.2开始,Dubbo 官方已经废弃原有的 gRPC 通信协议,使用 Triple 通信协议进行替代,Triple 通信协议完全兼容 gRPC 通信协议。 - 从 Dubbo
3.3开始,Dubbo 官方直接移除了 REST 通信协议的直接支持,使用 Triple 通信协议间接支持 REST 通信协议。
- Dubbo 3 支持的通信协议
| 通信协议 | 通信协议名 / 标识 | 传输层 | 核心特性 | 适用场景 | 是否为默认通信协议 |
|---|---|---|---|---|---|
| Dubbo 通信协议 | dubbo | TCP | 单一长连接、NIO 异步通信、高性能 | 高并发小数据量场景(消费者数 >> 提供者) | 是 |
| Triple 通信协议 | tri | HTTP/2 | 兼容 gRPC 生态、支持流式通信、跨语言能力增强 | 多语言交互、流式数据传输 | 否 |
| REST 通信协议 | rest | HTTP | 严格遵循 RESTful 规范、支持 JSON/XML 格式 | 浏览器调用、跨平台集成 | 否 |
| gRPC 通信协议 | grpc | HTTP/2 | Google 开源 RPC 框架、高性能跨语言通信 | 跨语言高性能服务调用 | 否 |
| Thrift 通信协议 | thrift | TCP | Apache 开源 RPC 框架、强类型接口定义 | 多语言服务交互 | 否 |
| Hessian 通信协议 | hessian | HTTP | 表单序列化、短连接、传输效率优于 WebService | 文件 / 视频等大数据传输 | 否 |
| HTTP 通信协议 | http | HTTP | 通用同步传输、支持表单序列化 | 混合数据大小场景 | 否 |
| Redis 通信协议 | redis | 文本通信协议 | 基于 Redis 的轻量级通信 | 缓存操作、简单消息队列 | 否 |
| MQTT 通信协议 | mqtt | TCP | 发布 / 订阅模式、低带宽占用 | 物联网设备通信 | 否 |
| WebService | webservice | HTTP | 基于 SOAP 通信协议、XML 序列化 | 传统企业系统集成 | 否 |
- 通信协议的选择对比
| 维度 | Triple | Dubbo | REST | gRPC |
|---|---|---|---|---|
| 性能 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 跨语言 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 流式通信 | 支持 | 不支持 | 不支持 | 支持 |
| 浏览器兼容 | 支持 | 不支持 | 直接支持 | 需要依赖 Gateway(网关) |
| 配置复杂度 | 低 | 低 | 中 | 高 |
Dubbo 通信协议的使用
更多关于 Dubbo 通信协议的介绍和使用案例可以看 这里。
序列化协议
- Dubbo 支持的序列化协议
| 序列化协议 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Hessian2(默认协议) | - 跨语言支持好 - 二进制序列化,协议紧凑 - 使用简单,Dubbo 原生支持 | - 性能中等,不是最高 - 不支持对象引用共享 | 默认通用场景,适合大多数 RPC 调用 |
| FastJson(JSON 格式) | - 可读性强,易于调试 - 跨语言方便,尤其适合前端交互 | - 文本冗余,体积大 - 性能低于二进制序列化协议(如 Hessian、Protobuf) | 需要与前端或非 Java 系统交互,或调试、日志记录场景 |
| GSON(JSON 格式) | - 可读性强,易于调试 - 跨语言方便,尤其适合前端交互 | - 文本冗余,体积大 - 性能低于二进制序列化协议(如 Hessian、Protobuf) | 需要与前端或非 Java 系统交互,或调试、日志记录场景 |
| Kryo | - 高性能,二进制序列化速度快 - 体积小 - 支持对象图 | - 不支持跨语言 - 需要手动注册类才能获得最佳性能 | 内部 Java 服务之间的高性能 RPC 调用,大数据领域用得较多 |
| FST(Fast-Serialization) | - 二进制序列化速度快,性能接近 Kryo - 使用更简单 | - 不支持跨语言 - 对类版本变化敏感 | 内部 Java 服务之间的高性能 RPC 调用 |
| Protobuf(Google Protocol Buffers) | - 高性能,二进制序列化速度快 - 体积小 - 强类型定义 - 跨语言支持好 | - 需要 .proto 文件定义- 学习成本高 | 跨语言、高性能 RPC 或需要协议稳定性的场景 |
| Protostuff(基于 Protobuf) | - 高性能二进制序列化,速度快、体积小 - 无需 .proto 文件定义,直接支持 Java POJO- 序列化 / 反序列化效率显著高于 Hessian2 | - 仅适用于 Java 环境(不支持跨语言) - 对象类结构变化后兼容性较差(需保持字段一致) | 对性能要求高的 Java 内部服务调用,如高并发、高吞吐的微服务场景 |
| Avro | - Hadoop 的子项目 - 支持动态 Schema - 跨语言支持好 - 二进制序列化 | - 性能不如 Protobuf - 使用复杂 | 大数据场景,如 Kafka、Hadoop,需要动态 Schema |
| Java 原生序列化 | - Java 原生支持,无需额外依赖 - 保留完整对象结构 - 二进制序列化 | - 体积大,性能低 - 安全性差(反序列化漏洞风险高) - 不支持跨语言 | 仅用于内部 Java 服务调试或原型验证,不建议生产使用 |
- Dubbo 序列化协议的选择建议
| 场景 | 推荐的序列化协议 | 说明 |
|---|---|---|
| 默认场景(通用 RPC 调用) | Hessian2 | Dubbo 默认的序列化协议,跨语言支持好、易用,性能中等,适合大多数场景 |
| Java 内部高性能 RPC 调用 | Protostuff、Kryo、FST | 二进制序列化速度快、体积小,适合 Java 内部服务之间的高性能调用 |
| 跨语言且追求极致性能 | Protobuf | 高性能、体积小、跨语言支持好,但需要 .proto 文件定义,学习成本较高 |
| 调试或日志场景 | FastJson | 可读性强,易于调试,适合调试、日志记录或与前端交互,但性能较低 |
| 不建议使用 | Java 原生序列化 | 性能低、体积大、安全性差(反序列化漏洞风险高),仅适合内部调试或原型验证 |
提示
- 各种 Java 的序列化库的性能测试比较结果可以看 这里 或者 GitHub Wiki。
- 更多关于 Dubbo 序列化协议的介绍和使用案例可以看 这里。
负载均衡策略
- Dubbo 提供了五种负载均衡策略,如下所示:
| 负载均衡策略 | 说明 | 适用场景 |
|---|---|---|
| RandomLoadBalance | 随机(默认策略),支持按权重设置随机概率 | 适用于请求量分散、服务性能相近的场景,能保证简单有效的负载均衡效果 |
| RoundRobinLoadBalance | 轮询,支持按公约后的权重设置轮询比率 | 适用于服务节点性能相近、需要保证调用次数均匀的场景 |
| LeastActiveLoadBalance | 最少活跃调用数,支持相同活跃数的权重随机 | 适用于服务性能差异较大或请求响应时间差异较大的场景,能提高请求分配效率 |
| ConsistentHashLoadBalance | 一致性哈希,相同参数的请求总是发送到同一个服务提供者 | 适用于需要保证请求一致性的场景,例如:缓存服务或用户会话粘性 |
| ShortestResponseLoadBalance | 最短响应时间优先,选择响应时间最短的服务提供者 | 适用于请求响应时间差异较大,且希望优先使用低延迟服务节点的场景 |

Dubbo 负载均衡策略的使用
集群容错策略
- Dubbo 提供了八种集群容错策略,如下所示:
| 集群容错策略 | 策略名称 | 策略说明 | 适用场景 |
|---|---|---|---|
| Failover | 故障转移 | 默认策略,当出现服务调用失败,重试调用其它服务器,默认重试 2 次,使用 retries 属性配置重试次数 | 通常用于读操作(幂等操作),如查询数据时可以容忍偶尔失败并重试的场景 |
| Failfast | 快速失败 | 消费者只发起一次调用,若失败则立即抛出异常,类似于 Failover 集群容错策略中重试次数设置为 0 的情况 | 通常用于写操作(非幂等操作),如数据插入操作,失败时不应该重试 |
| Failsafe | 失败安全 | 当消费者调用提供者出现异常时,只会打印异常,而不会抛出异常,即直接忽略本次消费操作,返回一个空结果 | 适用于不重要的操作,如日志记录或监控信息上报,出现失败不影响整体业务逻辑 |
| Failback | 失败自动恢复 | 在消费者调用失败后,返回一个空结果。Dubbo 会在内存中记录下(非持久化)该失败请求,并通过定时任务对失败的调用进行重试,且重试间隔默认是 5 秒(不可配置) | 通常用于消息通知操作,如异步通知,失败后可自动补偿 |
| Forking | 并行调用 | 消费者对于同一服务会并行调用多个提供者服务器,只要有一个成功就调用结束,并返回结果 | 通常用于实时性要求较高的读操作,但需要浪费更多的服务资源,可通过 forks = "2" 来设置最大并行数 |
| Broadcast | 广播调用 | 通过广播调用所有提供者,逐个调用,任意一个提供者报错则报错 | 适用于更新配置或通知所有服务提供者的场景,如缓存更新或者服务健康检查 |
| ZoneAware | 区域感知集群 | 当部署了多个机房或区域(Zone)时,优先调用同一机房的服务提供者,以降低跨区调用延迟和网络风险 | 适用于多区域部署场景,优先减少跨区域网络延迟的情况下实现高效负载均衡 |
| Mergeable | 结果合并集群 | 调用多个服务提供者,并将多个服务提供者的调用结果进行合并,最终返回合并后的结果 | 适用于需要聚合结果的场景,例如分布式查询、汇总统计等操作 |
Dubbo 集群容错策略的使用
FailbackCluster(失败自动恢复)的使用问题- 问题描述:
- 调用失败的记录只存储在内存中,应用重启或进程崩溃后,重试记录将会丢失,导致重试机制失效。
- 这种重试机制不适用于核心业务,比如支付交易、订单创建、库存扣减、用户积分更新等。
- 问题解决:
- 第一种方案:
- Dubbo 调用改为消息驱动,使用 MQ 做调用的缓冲与重试。
- (1) 生产者不直接调用 Dubbo 服务,而是先将消息写入 MQ 中;
- (2) 由一个消息消费服务订阅 MQ 消息,然后执行 Dubbo RPC 调用(基于 Dubbo 原生 API 实现泛化调用);
- (3) 如果 Dubbo RPC 调用失败,可以将消息写入到延迟队列中,直到 RPC 调用成功;
- (4) 如果重试调用次数达到最大重试次数,则将消息写入死信队列。
- (5) 为了避免重复调用,需要实现幂等性,比如使用数据库唯一约束(如
requestId字段)、Redis 缓存(如SETNX)等。
- Dubbo 调用改为消息驱动,使用 MQ 做调用的缓冲与重试。
- 第二种解决方案:
- (1) 自定义扩展
FailbackCluster的实现; - (2) 将失败记录写入外部存储(如 Redis、DB);
- (3) 创建后台线程,每隔固定时间扫描一次外部存储,尝试重新调用 Dubbo 服务(基于 Dubbo 原生 API 实现泛化调用);
- (4) 当重试调用成功返回时,将对应的失败记录从外部存储中删除,避免重复调用和数据堆积;
- (5) 应用重启后重新加载未完成的失败记录,并继续由后台线程执行重试逻辑;
- (6) 为了避免重复调用,需要实现幂等性,比如使用数据库唯一约束(如
requestId字段)、Redis 缓存(如SETNX)等。
- (1) 自定义扩展
- 第一种方案:
- 方案对比:
对比维度 MQ(Kafka / RabbitMQ / RocketMQ) Redis / MySQL 数据持久化 内置持久化到 Broker 持久化到外部存储,需要自己实现 应用重启恢复 重启后仍可消费未完成消息 重启后可读取失败记录,依赖自定义逻辑 重试保障 原生支持延迟重试和死信队列 需定时任务扫描和手动实现延迟 / 死信机制 吞吐能力 高,支持大规模并发 中等,定时扫描会成为性能瓶颈 延迟控制 原生支持延迟队列 延迟不可控,依赖扫描频率 实现复杂度 中等,需要部署 MQ,但重试逻辑成熟 低 - 中,不依赖额外中间件,但需自己实现重试和死信机制 适用场景 核心业务,如订单、支付、交易 弱异步业务,如日志、通知、监控埋点等,可容忍一定延迟或失败 维护成本 低,MQ 自带可靠机制 高,定时任务、重试逻辑和死信处理需自行维护
- 问题描述:
Dubbo 的泛化调用是什么
普通的 Dubbo 调用依赖接口类,会进行编译期类型检查,适合直接业务调用。而泛化调用则不依赖接口类,只通过接口名称和方法信息在运行时进行调用,参数类型通过字符串描述,适合消息化、动态调用、异步或重试场景。
Dubbo 深入理解
RMI 协议
Dubbo 支持使用 RMI 协议作为通信协议,但 RMI 的性能相对较低,不推荐用于高并发场景,更适合系统内部或旧系统集成场景。若需要高性能的通信,建议使用 Dubbo、Hessian、Thrift、gRPC 等通信协议。
RMI 协议的介绍
RMI 协议的简介
- Dubbo 的 RMI 协议是基于 Java 原生 RMI 实现的,要求服务消费者与服务提供者均为 Java 应用
- Dubbo 的 RMI 协议采用 JDK 标准的
java.rmi实现,并采用阻塞式短连接和 JDK 序列化(二进制格式) - 如果 Dubbo 使用 RMI 协议提供服务给外部访问,且应用里依赖了旧版本的
common-collections包的情况下,则可能会存在反序列化安全风险
RMI 协议的特性
- 连接个数:多连接
- 连接方式:短连接
- 传输协议:TCP 协议
- 传输方式:同步通信
- 跨语言支持:不支持跨语言
- 序列化协议:JDK 序列化(二进制格式)
- 注意事项:
- RMI 协议的性能相对较低,不推荐用于高并发场景
- 适用场景:
- 适用于针对 Java 的常规远程过程调用(RPC)
- 适用于与原生 Java RMI 服务互通,便于系统间集成与迁移
- 适用于系统内部或旧系统集成场景
- 适用范围:
- 适用于传入、传出参数数据量中等、消费者与提供者数量差不多的场景
- 适用于 Java 对象和文件等可序列化数据的传输
RMI 协议的注意事项
- 方法参数及返回值需要实现
Serializable接口 - Dubbo 配置中的超时时间(
timeout)对 RMI 协议无效,需要使用 JVM 启动参数设置 RMI 协议的超时时间(单位毫秒):1
2
3-Dsun.rmi.transport.connectionTimeout=3000 # 建立连接的超时时间
-Dsun.rmi.transport.tcp.responseTimeout=3000 # 响应超时时间(重点)
-Dsun.rmi.transport.proxy.connectTimeout=3000 # 代理连接的超时时间
- 方法参数及返回值需要实现
RMI 协议的使用
下载 Dubbo 使用 RMI 协议的案例代码
- Dubbo 使用 RMI 协议的完整案例代码可以直接从 GitHub 下载对应章节 dubbo-lesson-13。
RMI 服务接口的定义
- 如果服务接口继承了
java.rmi.Remote接口,可以和 Java 原生 RMI 服务相互操作- 服务提供者使用 Dubbo 的 RMI 协议暴露服务,服务消费者直接使用标准 RMI 接口调用服务;
- 或者,服务提供者使用标准 RMI 接口暴露服务,服务消费者使用 Dubbo 的 RMI 协议调用服务。
- 如果服务接口没有继承
java.rmi.Remote接口- Dubbo 默认会自动生成一个
com.xxx.XxxServiceRemote接口,并继承java.rmi.Remote接口,并以该接口暴露服务; - 但是,如果设置了
<dubbo:protocol name="rmi" codec="spring"/>,Dubbo 将不会自动生成Remote接口,而是使用 Spring 提供的RmiInvocationHandler接口来暴露服务,可以和 Spring 兼容。
- Dubbo 默认会自动生成一个
- 由于 Dubbo 的 RMI 协议是基于 Java 原生 RMI 实现的,并且使用 JDK 序列化(二进制格式)机制,因此 Dubbo 应用(基于 Java 开发)无需引入第三方包就可以直接使用 RMI 协议。
- 如果服务接口继承了
RMI 协议的的配置示例
- 服务提供者的配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14<!-- 应用信息 -->
<dubbo:application name="rmi-provider"/>
<!-- 注册中心 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!-- 使用 RMI 协议 -->
<dubbo:protocol name="rmi" port="1099"/>
<!-- 服务暴露 -->
<dubbo:service interface="com.example.api.HelloService" ref="helloServiceImpl"/>
<!-- 服务实现 -->
<bean id="helloServiceImpl" class="com.example.provider.HelloServiceImpl"/> - 服务消费者的配置
1
2
3
4
5
6
7
8<!-- 应用信息 -->
<dubbo:application name="rmi-consumer"/>
<!-- 注册中心 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!-- 服务引用 -->
<dubbo:reference id="helloService" interface="com.example.api.HelloService"/>
- 服务提供者的配置
HTTP 协议
HTTP 协议的介绍
HTTP 协议的简介
- 基于 HTTP 的远程过程调用协议,采用 Spring 的
HttpIvoker实现。 - 使用 HTTP 协议可以让服务通过 HTTP 端口暴露,便于浏览器或者非 Dubbo 客户端调用。
- Dubbo 通过 Protocol SPI 扩展支持 HTTP 协议,但功能有限。如果需要支持浏览器 JSON/HTTP 调用,通常需要配合
Jsonrpc4j等一起使用。
- 基于 HTTP 的远程过程调用协议,采用 Spring 的
HTTP 协议的特性
- 连接个数:多连接
- 连接方式:短连接
- 传输协议:HTTP 协议
- 传输方式:同步通信
- 序列化:JSON 序列化
- 跨语言支持:支持跨语言
- 适用范围:
- 服务提供者数量通常多于服务消费者数量
- 可通过浏览器直接访问和调试服务
- 参数可以通过 URL 或表单方式传递
- 暂不适合传输大文件
- 适用场景:
- 适合需要同时被应用程序和浏览器端 JavaScript 调用的服务
HTTP 协议的注意事项
- Dubbo 的 HTTP 协议并不是标准 REST/HTTP,而是基于 JSON-RPC 的 RPC 调用,强烈建议 Dubbo 使用 REST 协议来替代 HTTP 协议。
- Dubbo 的 HTTP 协议要求:
- 必须是 POST 请求
- HTTP 请求体必须严格符合的 JSON-RPC 格式
- Dubbo 使用 HTTP 协议后,可以通过
curl等网络工具直接调用 Provider 对外提供的 JSON-RPC 服务,URL 格式:http://ip:port/<接口全限定名>,比如:1
2
3
4# curl 调用命令
curl -X POST http://127.0.0.1:8030/com.clay.dubbo.service.DemoService \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0", "method":"sayHello","params":["Jim"],"id":1}'1
2# 返回结果
{"jsonrpc":"2.0","id":1,"result":"Hello Jim"}
HTTP 协议的使用
下载 Dubbo 使用 HTTP 协议的案例代码
- Dubbo 使用 HTTP 协议的完整案例代码可以直接从 GitHub 下载对应章节 dubbo-lesson-15。
HTTP 协议支持的 Server
- 目前在 Dubbo 2 中,HTTP 协议可以跑在以下几种不同的 Server 上,分别是:
Jetty:基于嵌入式 Jetty 的 HTTP Server,通过<dubbo:protocol name="rest" port="8080" server="jetty"/>来配置。Tomcat:基于嵌入式 Tomcat 的 HTTP Server,通过<dubbo:protocol name="rest" port="8080" server="tomcat"/>来配置。Servlet:采用外部应用服务器的 Servlet 容器(如外部 Tomcat)来做 HTTP Server,通过<dubbo:protocol name="rest" server="servlet"/>来配置,这里不需要配置端口,另外还需要在web.xml中做额外的其他配置。
- 目前在 Dubbo 2 中,HTTP 协议可以跑在以下几种不同的 Server 上,分别是:
HTTP 协议的配置示例
- 服务提供者中的配置:
1
2
3
4
5
6
7
8
9
10
11
12<!-- Dubbo 核心包 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.23</version>
</dependency>
<!-- Dubbo 的 HTTP 协议扩展 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-http</artifactId>
<version>2.7.23</version>
</dependency>1
2
3
4
5
6
7
8
9
10
11
12
13
14<!-- 应用信息 -->
<dubbo:application name="http-provider"/>
<!-- 注册中心 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!-- 使用 HTTP 协议 -->
<dubbo:protocol name="http" port="8030" server="tomcat"/>
<!-- 服务暴露 -->
<dubbo:service interface="com.example.api.HelloService" ref="helloServiceImpl"/>
<!-- 服务实现 -->
<bean id="helloServiceImpl" class="com.example.provider.HelloServiceImpl"/> - 服务消费者中的配置:
1
2
3
4
5
6
7
8
9
10
11
12<!-- Dubbo 核心包 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.23</version>
</dependency>
<!-- Dubbo 的 HTTP 协议扩展 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-http</artifactId>
<version>2.7.23</version>
</dependency>1
2
3
4
5
6
7
8<!-- 应用信息 -->
<dubbo:application name="http-consumer"/>
<!-- 注册中心 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!-- 服务引用 -->
<dubbo:reference id="helloService" interface="com.example.api.HelloService"/> - 当 Dubbo 成功使用 HTTP 协议作为通信协议后,可以通过
curl等网络工具直接调用 Provider 对外提供的 JSON-RPC 服务,这样服务就可以同时被应用程序和浏览器端 JavaScript 直接调用1
2
3
4# curl 调用命令
curl -X POST http://127.0.0.1:8030/com.clay.dubbo.service.DemoService \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0", "method":"sayHello","params":["Jim"],"id":1}'1
2# 返回结果
{"jsonrpc":"2.0","id":1,"result":"Hello Jim"}
- 服务提供者中的配置:
Thrift 协议
Thrift 协议的介绍
Thrift 协议的简介
- Apache Thrift 协议最初是由 Facebook 开源的高性能、跨语言的远程过程调用(RPC)协议,它通过接口定义语言(IDL)定义服务,并使用二进制编码格式进行高效的数据序列化和通信。
- 当前 Dubbo 的 Thrift 协议是对 Apache Thrift 原生协议的扩展,即在 Apache Thrift 原生协议的基础上添加了一些额外的头信息,比如
service name,magic number等。 - 使用 Dubbo 的 Thrift 协议时,需要先使用 Apache Thrift 的 IDL 编译器根据
.thrift文件编译生成相应的 Java 接口代码(使用流程类似 gRPC),后续版本中会在这方面做一些增强。 - Dubbo 的 Thrift 协议与 Apache Thrift 原生协议不兼容,因此无法直接实现跨语言互相调用,仅用在 Dubbo 内部服务之间的通信。
Thrift 协议的特性
- 连接个数:多连接(基于 TCP,支持并发连接)
- 连接方式:长连接(保持会话连接,减少 TCP 握手开销)
- 传输协议:TCP 协议
- 传输方式:同步通信
- 跨语言支持:不支持跨语言
- 序列化:Apache Thrift 原生协议自带的二进制序列化
- 适用范围:
- 服务接口通过
.thrift文件定义,需提前生成服务接口的代码 - 适合高性能、低延迟的 RPC 场景
- 服务接口通过
- 适用场景:
- 适合后端微服务间高频通信(如金融、游戏、即时通讯等领域)
- 适合对性能要求较高、接口定义稳定的 Dubbo 内部服务
Thrift 协议的使用步骤
- (1) 从 Thrift 官网 下载 Thrift
- Windows 平台:可以直接下载 Thrift 的二进制可执行文件,然后配置 Thrift 的环境变量
- Linux 平台:可以下载 Thrift 的源码包,然后手动编译安装 Thrift
- (2) 定义
.thrift文件(IDL) - (3) 根据
.thrift文件,通过thrift命令自动生成 Java 接口文件- 比如:
thrift -r -gen java HelloThrift.thrift
- 比如:
- (4) 根据自动生成的 Java 接口文件,实现相应的 Java 接口
- 特别注意,
thrift命令生成的Iface类是一个 Java 内部类,在 XML 中通过 Spring Bean 定义引用时,不可以用.符号连接,而是需要用$符号连接,比如:- 错误写法:
<dubbo:service interface="com.clay.dubbo.thrift.HelloThrift.Iface" ref="helloThrift" /> - 正确写法:
<dubbo:service interface="com.clay.dubbo.thrift.HelloThrift$Iface" ref="helloThrift" />
- 错误写法:
- (1) 从 Thrift 官网 下载 Thrift
Thrift 协议的注意事项
- Thrift 协议不支持
null值,也就是不能在 Thrift 协议中传递null值数据。 - Dubbo 的 Thrift 协议与 Apache Thrift 原生协议不兼容,因此无法直接实现跨语言互相调用,仅用在 Dubbo 内部服务之间的通信。
- Dubbo 的 Thrift 协议是基于 Apache Thrift 原生协议实现的扩展版本,用于 Dubbo 框架内部的 Thrift 序列化和网络传输支持,而非与 Apache Thrift 原生服务端或客户端直接交互;
- 如果需要通过 Dubbo 实现跨语言的 RPC 调用,建议使用 Dubbo Triple 协议(与 gRPC 完全兼容)。
- Dubbo 并未直接使用 Apache Thrift 原生协议的标准服务端(
TServer/TProcessor)实现,而是在其协议层之上做了二次封装。- Dubbo 的 Thrift 协议虽然使用了 Apache Thrift 原生协议的序列化与网络传输机制,但仍保留了 Dubbo 自身的:
- 服务导出机制
- 请求头与协议帧格式
- Service Name 识别方式
- Dubbo 的线程池模型与 Invoker 调用机制
- 由于这些封装,Dubbo 的 Thrift 协议在底层 Wire Protocol(报文格式 / 字节流格式) 上与 Apache Thrift 原生协议不同,即使接口定义一致,也无法互通。
- Dubbo 的 Thrift 协议虽然使用了 Apache Thrift 原生协议的序列化与网络传输机制,但仍保留了 Dubbo 自身的:
- Thrift 协议在 Dubbo 里的地位相当于 gRPC 协议 —— 它同时定义通信格式和数据序列化格式。
- Dubbo 的 Thrift 协议属于通信协议,但它内部包含了 Apache Thrift 原生协议自带的序列化机制;
- 在 Dubbo 的 Thrift 协议中,它并不使用 Dubbo 自己的序列化机制,而是直接使用 Apache Thrift 原生协议内置的编码 / 解码机制(即序列化机制);
- 在 Dubbo 的 Thrift 协议中,通信格式由 Apache Thrift 原生协议定义,因此它是一个带自有序列化机制的 Dubbo 通信协议,序列化机制由 Apache Thrift 原生协议提供。
- Thrift 协议不支持
Thrift 协议的使用
Thrift 的安装
- Thrift 编译安装
1 | # 下载源码压缩包 |
- Thrift 验证安装
1 | # 查看 Thrift 的版本 |
1 | /** |
- 若希望卸载 Thrift(比如想安装其他版本),可以执行以下命令
1 | # 卸载可执行文件 |
Thrift 的使用
下载 Dubbo 使用 Thrift 协议的案例代码
本节将演示 Dubbo 如何使用 Thrift 作为通信协议,完整的案例代码可以直接从 GitHub 下载对应章节 dubbo-lesson-14。
API 模块的代码与配置
- (1) 在 API 模块中,创建 Thrift 的 IDL 文件(比如
DemoThrift.thrift)
1 | namespace java com.clay.dubbo.thrift |
- (2) 在 API 模块中,通过
thrift命令生成 Java 接口文件,并拷贝到src/main/java目录下
1 | # 编译 Thrift 文件,生成 Java 接口文件 |
- (3) 在 API 模块中,引入 Dubbo 的 Thrift 依赖
1 | <!-- Dubbo 的 Thrift 协议扩展 --> |
特别注意
- 这个
dubbo-rpc-thrift模块实现了 Dubbo Protocol SPI 扩展,支持 Thrift RPC 调用;内部依赖了 Apache Thrift 库(libthrift),但大多数 Maven 坐标会自动拉取。 - Thrift 安装的二进制版本(即
thrift命令的版本),强烈建议跟 Apache Thrift 库(libthrift)的版本保证一致,否则通过thrift命令生成的 Java 代码可能无法正常编译。
Privoder 模块的代码与配置
- (1) 在 Provider 模块中,引用 API 模块与 Dubbo 的依赖
1 | <!-- API 模块 --> |
- (2) 在 Provider 模块中,实现 Thrift 生成的 Java 接口,并暴露 Dubbo 服务
1 | import com.clay.dubbo.thrift.DemoThrift; |
- (3) 在 Provider 模块中,添加对应的 Dubbo 配置内容
1 | server: |
Consumer 模块的代码与配置
- (1) 在 Consumer 模块中,引用 API 模块与 Dubbo 的依赖
1 | <!-- API 模块 --> |
- (2) 在 Consumer 模块中,通过 Thrift 生成的 Java 接口引用 Dubbo 服务
1 | import org.apache.dubbo.config.annotation.DubboReference; |
- (3) 在 Consumer 模块中,添加对应的 Dubbo 配置内容
1 | server: |
Consumer 调用 RPC 服务失败
- Dubbo 配置使用 Thrift 作为通信协议后,如果在 Consumer 调用 Provider 提供的服务时,出现超时现象,则可能是缺少
slf4j-log4j12包导致,详细说明请看 这里,但亲测可能还有其他原因会导致 RPC 调用超时。
Dubbo 协议
协议的简单介绍
Dubbo 协议是官方默认协议,其构成、特性、约束如下:
Dubbo 协议的构成
- 网络传输框架:Mina、Netty、Grizzy
- 序列化机制:Hessian2、FastJson、GSON、Protobuf、Kryo、FST、Avro、Java 等
- 线程分发策略:
all、direct、message、execution、connection - 线程池类型:
fixed、cached、limited、eager
Dubbo 协议的特性
- 连接个数:单连接(基于 TCP,支持多路复用)
- 连接方式:长连接(保持会话连接,减少 TCP 握手开销)
- 传输协议:TCP 协议
- 传输方式:NIO 异步通信
- 跨语言支持:不支持跨语言
- 序列化协议:Hessian2 二进制序列化
- 适用场景:
- 常规的远程过程调用(RPC)
- 适用范围:
- 服务消费者比服务提供者个数多
- 单一服务消费者无法压满服务提供者
- 传输的数据包较小(建议小于 100K)
- 尽量不要用 Dubbo 协议传输大文件(如音频、视频)或超大字符串
Dubbo 协议的注意事项
- 方法参数及返回值需要实现
Serializable接口 - 方法参数及返回值不能自定义实现
List、Map、Number、Date、Calendar等接口,只能用 JDK 自带的实现(如HashMap),因为 Hessian2 序列化会做特殊处理,自定义实现类中的属性值都会丢失 - 对于 Hessian2 序列化,只会传成员属性值和值的类型,不会传方法或者静态变量,兼容情况如下表所示:
![]()
- 方法参数及返回值需要实现
协议的注意事项
为什么 Dubbo 协议不适合传输大数据、大文件的场景?
- 核心原因:
- 因为 Dubbo 协议基于长连接和内存缓冲的 RPC 框架,它会将请求数据整体序列化、加载到内存再发送;
- 传输大文件会占用大量内存、阻塞网络 I/O 线程、导致 TCP 连接阻塞或超时;
- 大文件(如音频、视频)传输这类场景,更适合使用 HTTP、FTP、OSS 等流式传输协议。
- 举个例子:
- 前提条件:单一长连接、网络为千兆网卡(
1024Mbit = 128MByte); - 假设每个请求的数据包大小为
500KByte; - 假设每条连接最大只能压满
7MByte(不同环境可能不一样,仅供参考); - 单个服务提供者的最大 TPS:
128MByte / 500KByte = 262; - 单个服务消费者调用单个服务提供者的最大 TPS:
7MByte / 500KByte = 14; - 如果能接受相应的 TPS,可以考虑使用 Dubbo 协议,否则网络将成为性能瓶颈。
- 前提条件:单一长连接、网络为千兆网卡(
- 核心原因:
为什么 Dubbo 协议适合消费者比提供者个数多的场景?
- 核心原因:
- Dubbo 协议基于长连接和 NIO 异步通信,服务消费者与服务提供者之间会建立少量长连接,多个请求会复用这些长连接;
- 当服务消费者的数量远多于服务提供者时,连接可复用、资源占用低、性能更高;
- 反之,若服务提供者更多,会造成连接过多、内存和句柄开销大,效率反而下降。
- 举个例子:
- 前提条件:单一长连接、网络为千兆网卡(
1024Mbit = 128MByte); - 根据测试经验数据,每条连接最多只能压满
7MByte(不同环境可能不一样,仅供参考); - 理论上 1 个服务提供者需要 18(
128MByte / 7MByte = 18)个服务消费者才能压满网卡。
- 前提条件:单一长连接、网络为千兆网卡(
- 核心原因:
为什么 Dubbo 协议采用单一长连接和 NIO 异步通信?
- 核心原因:
- 因为在大多数场景下,服务提供者数量远少于服务消费者,例如某个核心服务只有几台提供者机器,却有成百上千个消费者同时调用;
- 如果使用传统的短连接(如 Hessian 服务),频繁地建立与销毁连接会导致服务端资源耗尽、性能下降;
- Dubbo 通过单一长连接维持服务消费者与服务提供者的通信,可以避免过多连接压垮服务端;
- 同时采用 NIO 异步通信和线程池复用,提升并发处理能力,有效避免 C10K 问题。
- 核心原因:
协议的详细配置
Dubbo 协议的配置示例如下:
1 | dubbo: |
协议的报文格式
- Dubbo 协议的报文结构图

- Dubbo 协议的消息头(固定 16 个字节)
| 字段 | 长度 | 作用 |
|---|---|---|
| Magic Number | 2 字节 | 魔数(通信协议标识),Dubbo 协议是固定值 0xdabb |
| Flag | 1 字节 | 请求 / 响应标识 |
| Status | 1 字节 | 响应状态 |
| Request ID | 8 字节 | 请求的唯一标识 |
| Body Length | 4 字节 | 请求体的长度 |
- Dubbo 协议的消息体(具体的业务操作内容)
| 字段名 | 说明 |
|---|---|
| RPC 版本 | Dubbo RPC 协议的版本号(比如 2.0.2) |
| 服务接口路径 | 服务接口的全限定名(比如 com.example.UserService) |
| 服务版本号 | 服务的版本信息(比如 1.0.0) |
| 服务方法名 | 调用的具体方法名称(比如 getUserById) |
| 参数描述符 | 方法参数类型描述字符串(比如 Ljava/lang/String; 表示 String 类型参数) |
| 参数值序列化 | 参数值的序列化内容(根据序列化协议如 Hessian2、JSON 等序列化后的字节流) |
| Dubbo 内置参数 | Dubbo 扩展的键值对参数(比如 path、timeout、group、loadbalance 等) |
协议的多路复用机制
Dubbo 协议(官方默认协议)支持单连接并发请求,因为它在 TCP 协议之上通过 Netty 和自定义通信协议实现了同一条 TCP 连接上的多路复用机制。每个请求都有唯一的 Request ID,响应则根据 Request ID 匹配到对应的调用线程,从而突破了 TCP 串行阻塞的限制。这就是 Dubbo 协议的多路复用机制,也是其高性能的主要原因。虽然 TCP 本质上是有序字节流,但 Dubbo 协议允许多个 RPC 请求在同一连接上交错传输,不必等待 “一个请求发送完、响应回来后才处理下一个请求”,实现了真正的并发请求。如果单连接仍存在性能瓶颈,可以通过调高 connections 参数,让 Consumer 为每个 Provider 建立多条物理 TCP 连接来分担流量。
特别注意
- Dubbo 协议的的多路复用机制,本质上复用的是 TCP 连接,也就是在同一条物理 TCP 连接上同时承载多个 RPC 请求和响应,从而减少连接建立的开销,并显著提升吞吐量。
- 这跟常说的 I/O 多路复用机制(比如
poll、epoll)完全不同,因为epoll等 I/O 多路复用机制本质上是在操作系统层面复用线程,两者概念完全不同。
基础认知:TCP 是有序字节流
- TCP 连接本身提供的只是可靠的、有序的字节流,并不关心应用层消息的边界。
- 如果应用层不做任何设计,那么它只能按顺序处理请求:
- (1) 客户端发出请求 A
- (2) 客户端等待服务端响应 A
- (3) 客户端才能发送请求 B
- 这就像一条 “管道”,只能一个一个地走,非常低效。
- 典型例子:HTTP/1.0 早期,每次请求必须等上一次响应返回后才能发起下一次,这就是队头阻塞(Head-of-Line Blocking)。
Dubbo 协议的报文结构
- Dubbo 协议的消息头固定 16 字节,包含关键信息:
字段 长度 作用 Magic Number 2 字节 魔数(通信协议标识),Dubbo 协议是固定值 0xdabbFlag 1 字节 请求 / 响应标识 Status 1 字节 响应状态 Request ID 8 字节 请求的唯一标识 Body Length 4 字节 请求体的长度 - Dubbo 协议的报文格式:
| Header(16 bytes) | Body(variable) |
- Dubbo 处理报文的流程:
- Consumer 发起 RPC 调用时,生成一个全局唯一的 Request ID
- Provider 处理完请求后,响应中带回相同的 Request ID
- Consumer 根据 Request ID 将响应结果分发到正确的调用线程
Dubbo 协议的多路复用机制
假设 Consumer 同时发起 3 个 RPC 请求(A、B、C),请求体会被打包,如下:
1
Consumer 的 TCP 发送缓冲区:|Header(ID=1)|BodyA|Header(ID=2)|BodyB|Header(ID=3)|BodyC|
Provider 可以并发处理这 3 个请求,并按处理完成的先后顺序返回结果,如下:
1
Consumer 的 TCP 接收缓冲区:|Header(ID=2)|RespB|Header(ID=1)|RespA|Header(ID=3)|RespC|
Consumer 接收到响应结果后:
- 解析消息头,取出 Request ID
- 将 RespB 分发给发起 B 请求的线程
- 将 RespA 分发给 A 请求线程
- 将 RespC 分发给 C 请求线程
这并行和串行的区别:
在 Dubbo 中,即使只有一条 TCP 连接,也能同时跑成百上千的并发请求。
特性 串行请求(无 Request ID) Dubbo 并发请求(带 Request ID) TCP 数据组织 每次只能传一条完整消息 多条消息交错在同一流里 发送端 必须等待上一个响应回来才能发送下一个请求 可以连续发送多个请求 响应顺序 响应必须严格按请求顺序返回 响应顺序与请求顺序无关 并发性能 低,队头阻塞严重 高,类似 HTTP/2 多路复用
Dubbo 协议多路复用机制的工作流程
- (1) 客户端发起调用
- Consumer 应用线程调用代理对象
- Dubbo 在客户端生成一个唯一的 Request ID 并将调用参数序列化
- (2) 写入 Netty Channel
- 所有调用请求都会被写入同一条 Netty Channel
- 发送数据时不会阻塞,只要 TCP 发送缓冲区有空间,就可以写入
- (3) 服务端并发处理
- Provider 使用线程池同时处理多个请求
- 不要求按请求顺序处理,哪个先处理完就先返回
- (4) 返回结果匹配
- Consumer 维护一个
ConcurrentHashMap<Long, Future> Key = Request ID,Value = 该请求对应的 Future- Consumer 收到响应时,根据响应里的 Request ID 找到对应 Future,并唤醒等待线程(调用线程)
- Consumer 维护一个
- (1) 客户端发起调用
Dubbo 协议为什么还会出现 “单连接阻塞” 问题
- 虽然 Dubbo 协议支持单连接并发请求,但底层 TCP 仍然是单通道,存在物理限制,主要体现在:
- TCP 层的队头阻塞(Head-of-Line Blocking)
- TCP 是有序字节流,一旦某个包丢失,后续所有数据都必须等待该包重传。
- 如果网络丢包率高,即使 Dubbo 应用层并行,仍会被 TCP 阻塞。
- Provider 中的线程池已满
- 如果 Provider 的处理线程不足,Consumer 的请求虽然发出去了,但在服务端被排队。
- 单连接带宽不足
- 多个请求争夺同一条 TCP 通道的带宽时,整体性能受限。
- TCP 层的队头阻塞(Head-of-Line Blocking)
- 虽然 Dubbo 协议支持单连接并发请求,但底层 TCP 仍然是单通道,存在物理限制,主要体现在:
Dubbo 多路复用机制的类比理解
- 可以将 Dubbo 多路复用机制类比为高速公路:
场景 类比 串行模式 高速公路只有 1 条车道,车必须按顺序行驶,前车没到终点,后车不能走 Dubbo 多路复用 高速公路有多条虚拟车道(Request ID),虽然实际物理只有 1 条道路,但每辆车都有自己的标记,不会互相等待 Dubbo 不同通信协议对单连接并发请求的支持情况
| 通信协议类型 | 是否支持单连接并发请求(多路复用机制) | 说明 |
|---|---|---|
| Dubbo 协议(默认) | ✅ 支持 | 通过自定义协议头 + Request ID 实现多路复用,这是 Dubbo 高性能的核心。 |
| Triple 协议(gRPC) | ✅ 支持 | 基于 HTTP/2,本身支持多路复用,天然具备并行能力。 |
| RMI、Hessian、WebService 等传统协议 | ❌ 不支持 | 基于同步调用或无多路复用设计,一个请求未返回响应前,后续请求无法在同一连接并行发送。 |
实战手写 Dubbo 客户端
学习目标与代码下载
- 这里将基于 Dubbo 协议的报文格式 + TCP 协议手写一个 Dubbo 客户端,目的是熟悉 Dubbo 协议的报文格式。
- 为了方便演示,服务提供者(Provider)使用的序列化协议为 FastJSON,而通信协议为 Dubbo 协议,注册中心使用 ZooKeeper。
- 由于篇幅有限,下面只给出服务消费者(Consumer)的核心代码,而服务提供者(Provider)的代码不再累述,完整的案例代码可以直接从 GitHub 下载对应章节
dubbo-lesson-09。
案例代码
- ZooKeeper 中的数据存储结构
1 | /dubbo |
- 核心依赖(服务消费者端)
1 | <dependency> |
- 核心代码(服务消费者端)
1 | import com.alibaba.fastjson.JSON; |
- 测试结果(服务消费者端),打印 RPC 调用结果时可能会出现部分内容乱码,但不影响整体的测试效果
1 | 未解码的 URL: dubbo%3A%2F%2F192.168.233.1%3A20880%2Fcom.clay.dubbo.service.DemoService%3Fanyhost%3Dtrue%26application%3Ddubbo-provider-application%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.clay.dubbo.service.DemoService%26metadata-type%3Dremote%26methods%3DsayHello%26pid%3D47362%26release%3D2.7.23%26serialization%3Dfastjson%26service.name%3DServiceBean%3A%2Fcom.clay.dubbo.service.DemoService%26side%3Dprovider%26timestamp%3D1758794536318 |

