协议概述
本文 triple 在 dubbo java 实现中的一些具体细节,配置方式、性能指标等
请参考文档其他部分了解 triple 协议规范规范 和 基本使用方式。本文只展开 triple 协议 Java 实现中的一些具体细节内容。
编程模式
使用 triple 协议时,开发者可以使用 Java Interface、Protobuf(IDL) 两种方式定义 RPC 服务,两种服务定义方式的协议能力是对等的,仅影响开发者的编程体验、序列化方式,具体选用那种开发模式,取决于使用者的业务背景。
Java接口
适合于 Dubbo 老用户、没有跨语言诉求的开发团队,具备学习成本低的优势,Dubbo2 老用户可以零成本切换到该协议。
服务定义范例:
1. public interface DemoService {
2. String sayHello(String name);
3. }
这种模式下,序列化方式可以选用 Hessian、JSON、Kryo、JDK、自定义扩展等任意编码协议。在使用体验上,可以说与老版本 dubbo 协议没有任何区别,只需要改一个 protocol 配置项即可,因此对于 dubbo 协议迁移到 triple 也会更平滑。
请通过【进阶学习 - 通信协议】查看 java Interface + Triple 协议的具体使用示例。
Protobuf
使用 Protobuf(IDL) 的方式定义服务,适合于当前或未来有跨语言诉求的开发团队,同一份 IDL 服务可同时用于 Java/Go/Node.js 等多语言微服务开发,劣势是学习成本较高。
1. syntax = "proto3";
2. option java_multiple_files = true;
3. package org.apache.dubbo.springboot.demo.idl;
5. message GreeterRequest {
6. string name = 1;
7. }
8. message GreeterReply {
9. string message = 1;
10. }
12. service Greeter{
13. rpc greet(GreeterRequest) returns (GreeterReply);
14. }
通过 Dubbo 提供的 protoc 编译插件,将以上 IDL 服务定义预编译为相关 stub 代码,其中就包含 Dubbo 需要的 Interface 接口定义,因此在后续编码上区别并不大,只不过相比于前面的用户自定义 Java Interface 模式,这里由插件自动帮我们生成 Interface 定义。
1. // Generated by dubbo protoc plugin
2. public interface Greeter extends org.apache.dubbo.rpc.model.DubboStub {
3. String JAVA_SERVICE_NAME = "org.apache.dubbo.springboot.demo.idl.Greeter";
4. String SERVICE_NAME = "org.apache.dubbo.springboot.demo.idl.Greeter";
6. org.apache.dubbo.springboot.demo.idl.GreeterReply greet(org.apache.dubbo.springboot.demo.idl.GreeterRequest request);
7. // more generated codes here...
8. }
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. @RestController
2. @RequestMapping("/demo")
3. public interface DemoService {
4. @GetMapping(value = "/hello")
5. String sayHello();
6. }
JAX-RS注解
JAX-RS 服务定义范例:
1. @Path("/demo")
2. public interface DemoService {
3. @GET
4. @Path("/hello")
5. String sayHello();
6. }
异常类型传递
Provider 端产生的业务异常需要作为响应值返回给 Consumer 客户端,消费端可以使用 try catch 捕获可能抛出的异常:
1. try {
2. greeterProxy.echo(REQUEST_MSG);
3. } catch (YourCustomizedException e) {
4. e.printStackTrace();
5. } catch (RpcException e) {
6. e.printStackTrace();
7. }
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. 枚举
- 原始pb代码
1. enum TrafficLightColor {
2. TRAFFIC_LIGHT_COLOR_INVALID = 0;
3. TRAFFIC_LIGHT_COLOR_UNSET = 1;
4. TRAFFIC_LIGHT_COLOR_GREEN = 2;
5. TRAFFIC_LIGHT_COLOR_YELLOW = 3;
6. TRAFFIC_LIGHT_COLOR_RED = 4;
7. }
- 生成的java代码

枚举是常量,因此采用大写
2.2. 数组
- 原始pb代码
1. message VipIDToRidReq {
2. repeated uint32 vipID = 1;
3. }
- 生成的java代码

底层实际上是1个ArrayList
2.3. 集合
PB不支持无序、不重复的集合,只能 借用数组实现,需要 自行去重。
2.4. 字典
- 原始pb代码
1. message BatchOnlineRes {
2. map<uint32, uint32> onlineMap = 1;//在线状态
3. }
- 生成的java代码

2.5. 嵌套
- 原始pb代码
1. message BatchAnchorInfoRes {
2. map<uint32, AnchorInfo> list = 1; //用户信息map列表
3. }
4. /*
5. * 对应接口的功能: 批量或单个获取用户信息
6. */
7. message AnchorInfo {
8. uint32 ownerUid = 1 [json_name="uid"]; //用户id
9. string nickName = 2 [json_name="nn"]; //用户昵称
10. string smallAvatar = 3 [json_name="savt"]; //用户头像全路径-小
11. string middleAvatar = 4 [json_name="mavt"]; //用户头像全路径-中
12. string bigAvatar = 5 [json_name="bavt"]; //用户头像全路径-大
13. string avatar = 6 [json_name="avt"]; //用户头像
14. }
- 生成的java代码

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>
最后修改 September 13, 2024: Refactor website structure (#2860) (1a4b998f54b)
