请参考文档其他部分了解 triple 协议规范规范 和 基本使用方式。本文只展开 triple 协议 Java 实现中的一些具体细节内容。
使用 triple 协议时,开发者可以使用 Java Interface
、Protobuf(IDL)
两种方式定义 RPC 服务,两种服务定义方式的协议能力是对等的,仅影响开发者的编程体验、序列化方式,具体选用那种开发模式,取决于使用者的业务背景。
适合于 Dubbo 老用户、没有跨语言诉求的开发团队,具备学习成本低的优势,Dubbo2 老用户可以零成本切换到该协议。
服务定义范例:
public interface DemoService { String sayHello(String name); }
这种模式下,序列化方式可以选用 Hessian、JSON、Kryo、JDK、自定义扩展等任意编码协议。在使用体验上,可以说与老版本 dubbo 协议没有任何区别,只需要改一个 protocol 配置项即可,因此对于 dubbo 协议迁移到 triple 也会更平滑。
请通过【进阶学习 - 通信协议】查看 java Interface + Triple 协议的具体使用示例。
使用 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 协议的具体使用示例。
是 | 否 | |
---|---|---|
公司的业务是否有用 Java 之外的其他语言,跨语言互通的场景是不是普遍? | Protobuf | Java 接口 |
公司里的开发人员是否熟悉 Protobuf,愿意接受 Protobuf 的额外成本吗? | Protobuf | Java 接口 |
是否有标准 gRPC 互通诉求? | Protobuf | Java 接口 |
是不是 Dubbo2 老用户,想平滑迁移到 triple 协议? | Java 接口 | Protobuf |
Triple
协议的流模式
从协议层来说,Triple
是建立在 HTTP2
基础上的,所以直接拥有所有 HTTP2
的能力,故拥有了分 streaming
和全双工的能力。
框架层来说,org.apache.dubbo.common.stream.StreamObserver
作为流的接口提供给用户,用于入参和出参提供流式处理。框架在收发 stream data 时进行相应的接口调用, 从而保证流的生命周期完整。
Streaming 是 Dubbo3 新提供的一种调用类型,在以下场景时建议使用流的方式:
Stream 分为以下三种。
服务端流式 RPC 类似于 Unary RPC,不同之处在于服务端会响应客户端的请求并返回消息流。在发送完所有消息后(通常是多条消息),服务端会发送状态信息(状态代码和可选状态消息)和可选的尾部元数据给客户端,这写状态信息发送完后服务器端流就结束了。一旦客户端通过 StreamObserver 接收到了以上所有了服务器消息,流就完成了。
客户端流式 RPC 类似于 Unary RPC,不同之处在于客户端向服务器发送消息流(通常包含多条消息)而不是单个消息。服务器以单个消息(以及其状态详细信息和可选的尾部元数据)进行响应 - 通常但不一定是在接收到所有客户端消息之后。
在双向流 RPC 中,客户端发起方法调用,服务端则接收客户端调用中的元数据、方法名称和截止日期,这样就启动了一次完整的双向流通道。服务器可以选择返回其初始元数据,或者等待客户端开始流式传输消息。
客户端和服务器端的流处理是特定于应用程序的。由于这两个流是独立的,客户端和服务器可以按任何顺序读取和写入消息。例如,服务器可以等到收到客户端的所有消息后再写消息,或者服务器和客户端可以玩“乒乓球”——服务器收到一个请求,然后发回一个响应,然后客户端根据响应发送另一个请求等等。
{{% alert title=“流的语义保证” color=“primary” %}}
关于 Streaming 的具体使用示例,请参见 Streaming 流式通信。
通过为 Java 接口增加注解,可以发布 rest 风格的 triple 服务,可在这里查看 具体代码示例
{{% alert title=“流的语义保证” color=“info” %}} 目前 rest 协议仅支持 Java 接口
服务定义模式,相比于 dubbo 和 triple 协议,rest 场景下我们需要为 Interface 增加注解,支持 Spring MVC、JAX_RS 两种注解。 {{% /alert %}}
如果你记得 triple 协议原生支持 cURL 访问,即类似 org.apache.dubbo.springboot.demo.idl.Greeter/greet
的访问模式。通过增加以上注解后,即可为 triple 服务额外增加 REST 风格访问支持,如 demo/greet
的 GET 请求。
Spring MVC 服务定义范例:
@RestController @RequestMapping("/demo") public interface DemoService { @GetMapping(value = "/hello") String sayHello(); }
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
类型返回:
对于计划从 Java 接口完全迁移到 Protobuf 的用户而言,这里的信息可供参考,用以了解类型迁移可能面临的限制,Protobuf 描述语言是否能完全描述 Java 数据类型。
本文对比了Protobuf和Java Interface这2种IDL的差异,帮助Dubbo协议开发者了解Protobuf,为后续转到Triple协议和Grpc协议做铺垫。
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 |
{{% alert title=“注意” color=“primary” %}} 在Java中,无符号的32位和64位整数使用它们的有符号对数来表示,顶部位只存储在符号位中。 {{% /alert %}}
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; }
枚举是常量,因此采用大写
message VipIDToRidReq { repeated uint32 vipID = 1; }
底层实际上是1个ArrayList
PB不支持无序、不重复的集合,只能 借用数组实现
,需要 自行去重
。
message BatchOnlineRes { map<uint32, uint32> onlineMap = 1;//在线状态 }
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"]; //用户头像 }
Feature | Java Interface | Protobuf | 备注 |
---|---|---|---|
方法重载 | √ | × | |
泛型/模板化 | √ | × | |
方法继承 | √ | × | |
嵌套定义 | √ | 部分支持 | PB仅支持message和enum嵌套 |
import文件 | √ | √ | |
字段为null | √ | × | |
多个入参 | √ | × | PB仅支持单入参 |
0个入参 | √ | × | PB必须有入参 |
0个出参 | √ | × | PB必须有出参 |
入参/出参为抽象类 | √ | × | PB的入参/出参必须为具象类 |
入参/出参为接口 | √ | × | PB的入参/出参必须为具象类 |
入参/出参为基础类型 | √ | × | PB的入参/出参必须为结构体 |