回顾
大家好,我是老马。
最近 dubbo3.x 在公司内部分享,于是想系统梳理一下。
总体思路是官方文档入门+一些场景的问题思考+源码解析学习。
协议概述
本文 triple 在 dubbo java 实现中的一些具体细节,配置方式、性能指标等
请参考文档其他部分了解 triple 协议规范规范 和 基本使用方式。
本文只展开 triple 协议 Java 实现中的一些具体细节内容。
编程模式
使用 triple 协议时,开发者可以使用 Java Interface
、Protobuf(IDL)
两种方式定义 RPC 服务,两种服务定义方式的协议能力是对等的,仅影响开发者的编程体验、序列化方式,具体选用那种开发模式,取决于使用者的业务背景。
Java接口
适合于 Dubbo 老用户、没有跨语言诉求的开发团队,具备学习成本低的优势,Dubbo2 老用户可以零成本切换到该协议。
服务定义范例:
1
2
3public interface DemoService {
String sayHello(String name);
}
这种模式下,序列化方式可以选用 Hessian、JSON、Kryo、JDK、自定义扩展等任意编码协议。
在使用体验上,可以说与老版本 dubbo 协议没有任何区别,只需要改一个 protocol 配置项即可,因此对于 dubbo 协议迁移到 triple 也会更平滑。
请通过【进阶学习 - 通信协议】查看 java Interface + Triple 协议的具体使用示例。
Protobuf
使用 Protobuf(IDL) 的方式定义服务,适合于当前或未来有跨语言诉求的开发团队,同一份 IDL 服务可同时用于 Java/Go/Node.js 等多语言微服务开发,劣势是学习成本较高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14syntax = "proto3";
option java_multiple_files = true;
package org.apache.dubbo.springboot.demo.idl;
message GreeterRequest {
string name = 1;
}
message GreeterReply {
string message = 1;
}
service Greeter{
rpc greet(GreeterRequest) returns (GreeterReply);
}
通过 Dubbo 提供的 protoc 编译插件,将以上 IDL 服务定义预编译为相关 stub 代码,其中就包含 Dubbo 需要的 Interface 接口定义,因此在后续编码上区别并不大,只不过相比于前面的用户自定义 Java Interface 模式,这里由插件自动帮我们生成 Interface 定义。
1
2
3
4
5
6
7
8// Generated by dubbo protoc plugin
public interface Greeter extends org.apache.dubbo.rpc.model.DubboStub {
String JAVA_SERVICE_NAME = "org.apache.dubbo.springboot.demo.idl.Greeter";
String SERVICE_NAME = "org.apache.dubbo.springboot.demo.idl.Greeter";
org.apache.dubbo.springboot.demo.idl.GreeterReply greet(org.apache.dubbo.springboot.demo.idl.GreeterRequest request);
// more generated codes here...
}
Protobuf 模式支持序列化方式有 Protobuf Binary、Protobuf JSON 两种模式。最后,请通过【进阶学习 - 通信协议】查看 Protobuf (IDL) + Triple 协议的具体使用示例。
3. 我该使用哪种编程模式,如何选择?
是 | 否 | |
---|---|---|
公司的业务是否有用 Java 之外的其他语言,跨语言互通的场景是不是普遍? | Protobuf | Java 接口 |
公司里的开发人员是否熟悉 Protobuf,愿意接受 Protobuf 的额外成本吗? | Protobuf | Java 接口 |
是否有标准 gRPC 互通诉求? | Protobuf | Java 接口 |
是不是 Dubbo2 老用户,想平滑迁移到 triple 协议? | Java 接口 | Protobuf |
Streaming流式通信
流实现原理
Triple
协议的流模式
- 从协议层来说,
Triple
是建立在HTTP2
基础上的,所以直接拥有所有HTTP2
的能力,故拥有了分streaming
和全双工的能力。 - 框架层来说,
org.apache.dubbo.common.stream.StreamObserver
作为流的接口提供给用户,用于入参和出参提供流式处理。框架在收发 stream data 时进行相应的接口调用, 从而保证流的生命周期完整。
适用场景
Streaming 是 Dubbo3 新提供的一种调用类型,在以下场景时建议使用流的方式:
- 接口需要发送大量数据,这些数据无法被放在一个 RPC 的请求或响应中,需要分批发送,但应用层如果按照传统的多次 RPC 方式无法解决顺序和性能的问题,如果需要保证有序,则只能串行发送
- 流式场景,数据需要按照发送顺序处理, 数据本身是没有确定边界的
- 推送类场景,多个消息在同一个调用的上下文中被发送和处理
Stream 分为以下三种。
SERVER_STREAM(服务端流)
服务端流式 RPC 类似于 Unary RPC,不同之处在于服务端会响应客户端的请求并返回消息流。在发送完所有消息后(通常是多条消息),服务端会发送状态信息(状态代码和可选状态消息)和可选的尾部元数据给客户端,这写状态信息发送完后服务器端流就结束了。一旦客户端通过 StreamObserver 接收到了以上所有了服务器消息,流就完成了。
CLIENT_STREAM(客户端流)
客户端流式 RPC 类似于 Unary RPC,不同之处在于客户端向服务器发送消息流(通常包含多条消息)而不是单个消息。服务器以单个消息(以及其状态详细信息和可选的尾部元数据)进行响应 - 通常但不一定是在接收到所有客户端消息之后。
BIDIRECTIONAL_STREAM(双向流)
在双向流 RPC 中,客户端发起方法调用,服务端则接收客户端调用中的元数据、方法名称和截止日期,这样就启动了一次完整的双向流通道。服务器可以选择返回其初始元数据,或者等待客户端开始流式传输消息。
客户端和服务器端的流处理是特定于应用程序的。由于这两个流是独立的,客户端和服务器可以按任何顺序读取和写入消息。例如,服务器可以等到收到客户端的所有消息后再写消息,或者服务器和客户端可以玩”乒乓球”——服务器收到一个请求,然后发回一个响应,然后客户端根据响应发送另一个请求等等。
流的语义保证
- 提供消息边界,可以方便地对消息单独处理
- 严格有序,发送端的顺序和接收端顺序一致
- 全双工,发送不需要等待
- 支持取消和超时
关于 Streaming 的具体使用示例,请参见 Streaming 流式通信。
REST 支持
通过为 Java 接口增加注解,可以发布 rest 风格的 triple 服务,可在这里查看 具体代码示例
流的语义保证
目前 rest 协议仅支持 Java 接口
服务定义模式,相比于 dubbo 和 triple 协议,rest 场景下我们需要为 Interface 增加注解,支持 Spring MVC、JAX_RS 两种注解。
如果你记得 triple 协议原生支持 cURL 访问,即类似 org.apache.dubbo.springboot.demo.idl.Greeter/greet
的访问模式。通过增加以上注解后,即可为 triple 服务额外增加 REST 风格访问支持,如 demo/greet
的 GET 请求。
Spring Web注解
Spring MVC 服务定义范例:
1
2
3
4
5
6@RestController
@RequestMapping("/demo")
public interface DemoService {
@GetMapping(value = "/hello")
String sayHello();
}
JAX-RS注解
JAX-RS 服务定义范例:
1
2
3
4
5
6@Path("/demo")
public interface DemoService {
@GET
@Path("/hello")
String sayHello();
}
异常类型传递
Provider 端产生的业务异常需要作为响应值返回给 Consumer 客户端,消费端可以使用 try catch
捕获可能抛出的异常:
1
2
3
4
5
6
7try {
greeterProxy.echo(REQUEST_MSG);
} catch (YourCustomizedException e) {
e.printStackTrace();
} catch (RpcException e) {
e.printStackTrace();
}
Dubbo 框架会在 provider 侧根据如下流程发送异常类型响应,不是所有业务异常都能原样返回,对于无法处理的异常类型,都会被框架封装成 RpcException
类型返回:
附录
Protobuf与Java原生数据类型对比
对于计划从 Java 接口完全迁移到 Protobuf 的用户而言,这里的信息可供参考,用以了解类型迁移可能面临的限制,Protobuf 描述语言是否能完全描述 Java 数据类型。
本文对比了Protobuf和Java Interface这2种IDL的差异,帮助Dubbo协议开发者了解Protobuf,为后续转到Triple协议和Grpc协议做铺垫。
1. 数据类型
1.1. 基本类型
ptoto类型 | java类型 |
---|---|
double | double |
float | float |
int32 | int |
int64 | long |
uint32 | int[注] |
uint64 | long[注] |
sint32 | int |
sint64 | long |
fixed32 | int[注] |
fixed64 | long[注] |
sfixed32 | int |
sfixed64 | long |
bool | boolean |
string | String |
bytes | ByteString |
注意
在Java中,无符号的32位和64位整数使用它们的有符号对数来表示,顶部位只存储在符号位中。
2. 复合类型
2.1. 枚举
1
2
3
4
5
6
7enum TrafficLightColor {
TRAFFIC_LIGHT_COLOR_INVALID = 0;
TRAFFIC_LIGHT_COLOR_UNSET = 1;
TRAFFIC_LIGHT_COLOR_GREEN = 2;
TRAFFIC_LIGHT_COLOR_YELLOW = 3;
TRAFFIC_LIGHT_COLOR_RED = 4;
}
枚举是常量,因此采用大写
2.2. 数组
1
2
3message VipIDToRidReq {
repeated uint32 vipID = 1;
}
底层实际上是1个ArrayList
2.3. 集合
PB不支持无序、不重复的集合,只能 借用数组实现
,需要 自行去重
。
2.4. 字典
1
2
3message BatchOnlineRes {
map<uint32, uint32> onlineMap = 1;//在线状态
}
2.5. 嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14message BatchAnchorInfoRes {
map<uint32, AnchorInfo> list = 1; //用户信息map列表
}
/*
* 对应接口的功能: 批量或单个获取用户信息
*/
message AnchorInfo {
uint32 ownerUid = 1 [json_name="uid"]; //用户id
string nickName = 2 [json_name="nn"]; //用户昵称
string smallAvatar = 3 [json_name="savt"]; //用户头像全路径-小
string middleAvatar = 4 [json_name="mavt"]; //用户头像全路径-中
string bigAvatar = 5 [json_name="bavt"]; //用户头像全路径-大
string avatar = 6 [json_name="avt"]; //用户头像
}
3. 字段默认值
- 对于字符串,默认值为空字符串。
- 对于字节,默认值为空字节。
- 对于bools,默认值为false。
- 对于数字类型,默认值为零。
- 对于枚举,默认值为第一个定义的枚举值,它必须为0。
- 对于消息字段,未设置字段。 它的确切值是语言相关的。 有关详细信息,请参阅生成的代码指南。
4. 整体结构
Feature | Java Interface | Protobuf | 备注 |
---|---|---|---|
方法重载 | √ | × | |
泛型/模板化 | √ | × | |
方法继承 | √ | × | |
嵌套定义 | √ | 部分支持 | PB仅支持message和enum嵌套 |
import文件 | √ | √ | |
字段为null | √ | × | |
多个入参 | √ | × | PB仅支持单入参 |
0个入参 | √ | × | PB必须有入参 |
0个出参 | √ | × | PB必须有出参 |
入参/出参为抽象类 | √ | × | PB的入参/出参必须为具象类 |
入参/出参为接口 | √ | × | PB的入参/出参必须为具象类 |
入参/出参为基础类型 | √ | × | PB的入参/出参必须为结构体 |
5. 社区资料
- 社区主页地址:https://developers.google.cn/protocol-buffers/
- 社区开源地址:https://github.com/google/protobuf
- 相关jar的maven:https://search.maven.org/search?q=com.google.protobuf
chat
triple 如何保证高性能
Dubbo 3.0 的 Triple 协议(基于 HTTP/2 + gRPC 标准扩展)的高性能主要源于其协议设计、传输层优化以及对现代 RPC 需求的深度适配。
以下是其高性能实现的关键技术点,以及它如何保证与传统 RPC 协议(如 Dubbo 2.x 的私有协议)相当甚至更高的效率:
1. 基于 HTTP/2 的传输层优化
- 多路复用(Multiplexing)
HTTP/2 支持在单个 TCP 连接上并发处理多个请求/响应(通过 Stream ID 区分),避免了传统 HTTP/1.1 的队头阻塞问题。这使得 Triple 协议在高并发场景下能显著减少连接数,降低资源消耗,提升吞吐量。 - 头部压缩(HPACK)
使用 HPACK 算法压缩 HTTP 头部,减少冗余数据传输,降低网络开销。 - 二进制帧传输
HTTP/2 的二进制帧格式比文本协议(如 HTTP/1.1)更高效,解析速度更快,适合高吞吐场景。
2. 高效的序列化机制
- 默认支持 Protobuf(Protocol Buffers)
Triple 协议默认使用 Protobuf 作为序列化方式,其二进制编码紧凑、解析速度快,显著减少数据体积和序列化/反序列化时间。 - 兼容多种序列化扩展
支持 JSON、Hessian2、Kryo 等序列化方式,用户可根据业务场景选择最优方案。
3. 协议层的精简设计
- 精简协议头
相比 Dubbo 2.x 的私有协议,Triple 协议通过复用 HTTP/2 的头部和标准 gRPC 协议定义,减少了自定义协议头的冗余字段,降低传输开销。 - 统一元数据管理
使用 HTTP/2 的 Header 传递元数据(如路由信息、超时配置),避免私有协议中复杂的二进制封装逻辑。
4. 全双工通信与流式支持
- 支持 Reactive Stream 模型
Triple 协议基于 HTTP/2 的流式特性,实现了请求/响应流、客户端流、服务端流、双向流四种通信模式,适用于大数据量分片传输或实时交互场景(如物联网、实时日志流)。 - 异步非阻塞 IO
结合 Netty 等异步框架,Triple 协议在传输层实现了非阻塞 IO,充分利用多核 CPU,避免线程阻塞。
5. 服务治理与性能调优
- 无缝集成 Dubbo 服务治理
Triple 协议兼容 Dubbo 的负载均衡、熔断、限流等机制,确保高性能的同时保障服务稳定性。 - 连接池优化
通过复用 HTTP/2 的长连接,减少 TCP 握手和 TLS 协商的开销,提升连接利用率。 - 线程模型优化
Dubbo 3.0 的 I/O 线程与业务线程分离,避免业务逻辑阻塞网络通信。
6. 对云原生的深度适配
- 兼容 gRPC 生态
Triple 协议完全兼容 gRPC 标准,可直接与 gRPC 服务互通,复用 gRPC 网关、监控等工具链。 - 支持 TLS 加密与 ALPN 协商
通过标准化的 TLS 加密和 ALPN(应用层协议协商),无缝适配 Service Mesh(如 Istio)等云原生基础设施。
7. 与传统 RPC 的性能对比
- 优势场景
- 高并发场景:HTTP/2 多路复用显著优于传统 RPC 的单一连接模型。
- 流式处理:传统 RPC 通常仅支持简单请求-响应,而 Triple 的流式能力更适合复杂交互。
- 性能持平
在简单请求-响应场景下,Triple 协议通过优化序列化和协议头,性能与 Dubbo 2.x 私有协议基本持平。
总结
Triple 协议的高性能来源于对 HTTP/2 底层能力的充分利用、协议层的精简设计、高效的序列化机制,以及对云原生架构的深度适配。
它既保留了传统 RPC 的高效性,又通过标准化协议扩展了流式通信和跨语言互通能力,成为 Dubbo 3.0 面向未来的核心协议。
对于开发者而言,Triple 协议无需牺牲性能即可获得更强大的功能和更广泛的生态兼容性。
参考资料
https://cn.dubbo.apache.orghttps://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/protocol/overview/