协议概述
本文 triple 在 dubbo java 实现中的一些具体细节,配置方式、性能指标等
请参考文档其他部分了解 triple 协议规范规范 和 基本使用方式。本文只展开 triple 协议 Java 实现中的一些具体细节内容。
编程模式
使用 triple 协议时,开发者可以使用 Java Interface
、Protobuf(IDL)
两种方式定义 RPC 服务,两种服务定义方式的协议能力是对等的,仅影响开发者的编程体验、序列化方式,具体选用那种开发模式,取决于使用者的业务背景。
Java接口
适合于 Dubbo 老用户、没有跨语言诉求的开发团队,具备学习成本低的优势,Dubbo2 老用户可以零成本切换到该协议。
服务定义范例:
public 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 等多语言微服务开发,劣势是学习成本较高。
syntax = "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 定义。
// 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 服务定义范例:
@RestController
@RequestMapping("/demo")
public interface DemoService {
@GetMapping(value = "/hello")
String sayHello();
}
JAX-RS注解
JAX-RS 服务定义范例:
@Path("/demo")
public interface DemoService {
@GET
@Path("/hello")
String sayHello();
}
异常类型传递
Provider 端产生的业务异常需要作为响应值返回给 Consumer 客户端,消费端可以使用 try catch
捕获可能抛出的异常:
try {
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. 枚举
- 原始pb代码
enum 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;
}
- 生成的java代码
枚举是常量,因此采用大写
2.2. 数组
- 原始pb代码
message VipIDToRidReq {
repeated uint32 vipID = 1;
}
- 生成的java代码
底层实际上是1个ArrayList
2.3. 集合
PB不支持无序、不重复的集合,只能 借用数组实现
,需要 自行去重
。
2.4. 字典
- 原始pb代码
message BatchOnlineRes {
map<uint32, uint32> onlineMap = 1;//在线状态
}
- 生成的java代码
2.5. 嵌套
- 原始pb代码
message 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"]; //用户头像
}
- 生成的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)