协议概述

本文 triple 在 dubbo java 实现中的一些具体细节,配置方式、性能指标等

请参考文档其他部分了解 triple 协议规范规范基本使用方式。本文只展开 triple 协议 Java 实现中的一些具体细节内容。

编程模式

使用 triple 协议时,开发者可以使用 Java InterfaceProtobuf(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 之外的其他语言,跨语言互通的场景是不是普遍?ProtobufJava 接口
公司里的开发人员是否熟悉 Protobuf,愿意接受 Protobuf 的额外成本吗?ProtobufJava 接口
是否有标准 gRPC 互通诉求?ProtobufJava 接口
是不是 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 服务,可在这里查看 具体代码示例

如果你记得 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 类型返回:

triple-exception

附录

Protobuf与Java原生数据类型对比

对于计划从 Java 接口完全迁移到 Protobuf 的用户而言,这里的信息可供参考,用以了解类型迁移可能面临的限制,Protobuf 描述语言是否能完全描述 Java 数据类型。

本文对比了Protobuf和Java Interface这2种IDL的差异,帮助Dubbo协议开发者了解Protobuf,为后续转到Triple协议和Grpc协议做铺垫。

1. 数据类型

1.1. 基本类型
ptoto类型java类型
doubledouble
floatfloat
int32int
int64long
uint32int[注]
uint64long[注]
sint32int
sint64long
fixed32int[注]
fixed64long[注]
sfixed32int
sfixed64long
boolboolean
stringString
bytesByteString

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代码

image

枚举是常量,因此采用大写

2.2. 数组
  • 原始pb代码
message VipIDToRidReq {
    repeated uint32 vipID = 1;
}
  • 生成的java代码

image

底层实际上是1个ArrayList

2.3. 集合

PB不支持无序、不重复的集合,只能 借用数组实现,需要 自行去重

2.4. 字典
  • 原始pb代码
message BatchOnlineRes {
    map<uint32, uint32> onlineMap = 1;//在线状态
}
  • 生成的java代码

image

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代码

image

3. 字段默认值

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于bools,默认值为false。
  • 对于数字类型,默认值为零。
  • 对于枚举,默认值为第一个定义的枚举值,它必须为0。
  • 对于消息字段,未设置字段。 它的确切值是语言相关的。 有关详细信息,请参阅生成的代码指南。

4. 整体结构

FeatureJava InterfaceProtobuf备注
方法重载×
泛型/模板化×
方法继承×
嵌套定义部分支持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) (1a4b998f54)