Dubbo 协议迁移到 Triple 协议指南
Triple 简介
关于 Triple
协议的格式和原理,请参考 RPC 通信协议
根据 Triple 设计目标,Triple
协议具有以下优势
- 能够进行跨语言交互。传统的多种语言和多种 SDK 模式以及 Mesh 跨语言模式都需要更通用和可扩展的数据传输协议。
- 提供更完整的请求模型。除了支持传统的请求/响应模型(Unary 单向通信)外,还支持流(Stream 流式通信)和双向(Bidirectional 双向通信)。
- 易于扩展,高渗透性,包括但不限于追踪/监控等支持,也应该被各级设备所识别,网关设施等可以识别数据包,对服务网格部署友好,降低用户理解难度。
- 完全兼容 grpc,客户端/服务器可以与原生 grpc 客户端通信。
- 可以复用现有 grpc 生态系统中的组件,以满足云原生场景下的跨语言、跨环境和跨平台互通需求。
对于当前使用其他协议的 Dubbo 用户,框架提供了与现有序列化方法兼容的迁移能力。在不影响现有在线业务的情况下,迁移协议的成本几乎为零。
需要新增连接 Grpc 服务的 Dubbo 用户可以直接使用 Triple 协议完成连接,无需单独引入 grpc 客户端完成。不仅可以保留现有的 Dubbo 易用性,还可以降低程序的复杂性和开发维护成本,无需额外适配和开发即可接入现有生态。
对于需要网关接入的 Dubbo 用户,Triple 协议提供了一种更原生的方式,使网关开发或使用开源 grpc 网关组件更加容易。网关可以选择不解析有效负载,这大大提高了性能。在使用 Dubbo 协议时,与语言相关的序列化方法是网关的一大痛点,传统的 HTTP 到 Dubbo 方法对于跨语言序列化几乎无能为力。同时,由于 Triple 的协议元数据存储在请求头中,网关可以轻松实现自定义需求,例如路由和限流。
Dubbo2 协议迁移过程
Dubbo2 用户使用 dubbo 协议 + 自定义序列化,例如 hessian2 来完成远程调用。
默认情况下,Grpc 只支持 Protobuf 序列化,它不支持 Java 语言中的多参数和方法重载。
在 Dubbo3 的开始,一个目标是与 Dubbo2 完全兼容。因此,为了保证 Dubbo2 的平滑升级,Dubbo 框架做了大量的工作来保证升级的无缝衔接。目前,默认序列化与 Dubbo2 一致,为 hessian2
。
因此,如果您决定升级到 Dubbo3 的 Triple
协议,您只需要在配置中将协议名称修改为 tri
(注意:不是 triple)。
接下来我们以一个 [项目] (https://github.com/apache/dubbo-samples/tree/master/3-extensions/protocol/dubbo-samples-triple/src/main/java/ org/apache/dubbo/sample/tri/migration) 为例,如何安全地逐步升级。
- 只使用
dubbo
协议启动provider
和consumer
,并完成调用。 - 使用
dubbo
和tri
协议启动provider
,使用dubbo
协议启动consumer
,并完成调用。 - 只使用
tri
协议启动provider
和consumer
,并完成调用。
定义服务
- 定义接口
public interface IWrapperGreeter {
//...
/**
* This is a normal interface, not serialized using pb
*/
String sayHello(String request);
}
- 实现类如下
public class IGreeter2Impl implements IWrapperGreeter {
@Override
public String sayHello(String request) {
return "hello," + request;
}
}
只使用 dubbo 协议
为了保证兼容性,我们首先将部分提供者升级到 dubbo3
版本,并使用 dubbo
协议。
使用 dubbo
协议启动一个 [Provider
] (https://github.com/apache/dubbo-samples/blob/master/3-extensions/protocol/dubbo-samples-triple/src/main/java/org /apache/dubbo/sample/tri/migration/ApiMigrationDubboProvider.java) 和 Consumer
,完成调用,输出如下:
同时使用 dubbo 和 triple 协议
为了升级在线服务,不可能同时完成提供者和消费者的升级。需要逐步操作以确保业务稳定性。在第二步中,提供者提供了一种双协议方式,同时支持 dubbo + tri 客户端。
结构如图所示:
根据推荐的升级步骤,提供者已经支持 tri 协议,所以 dubbo3 的消费者可以直接使用 tri 协议
使用 dubbo
协议和 triple
协议启动 [Provider
](https://github.com/apache/dubbo-samples/blob/master/3-extensions/protocol/dubbo-samples-triple/src/main /java/org/apache/dubbo/sample/tri/migration/ApiMigrationBothProvider.java)和 [Consumer
](https://github.com/apache/dubbo-samples/blob/master/3-extensions/protocol/ dubbo-samples-triple/src/main/java/org/apache/dubbo/sample/tri/migration/ApiMigrationBothConsumer.java),完成调用,输出如下
仅使用 triple 协议
当所有消费者都升级到支持 Triple
协议的版本时,提供者可以切换为仅使用 Triple
协议启动
结构如图所示:
[Provider](https://github.com/apache/dubbo-samples/blob/master/3-extensions/protocol/dubbo-samples-triple/src/main/java/org/apache/dubbo/sample/tri/ migration/ApiMigrationTriProvider.java)和 [Consumer](https://github.com/apache/dubbo-samples/blob/master/3-extensions/protocol/dubbo-samples-triple/src/main/java/org/apache/dubbo/sample/tri /migration/ApiMigrationTriConsumer.java)完成调用,输出如下
实现原理
通过上述升级过程,我们可以通过修改协议类型轻松完成升级。框架是如何帮助我们做到这一点的?
通过 Triple
协议的介绍,我们知道 Dubbo3 中 Triple
的数据类型是一个 protobuf
对象,那么为什么非 protobuf
的 Java 对象也可以正常传输呢?
这里 Dubbo3 使用了一种巧妙的设计,首先判断参数类型是否为 protobuf
对象,如果不是,则使用 protobuf
对象包装 request
和 response
,从而屏蔽了其他序列化带来的复杂性。在 wrapper
对象内部声明序列化类型以支持序列化扩展。
包装器的 protobuf
的 IDL 如下
syntax = "proto3";
package org.apache.dubbo.triple;
message TripleRequestWrapper {
//hessian4
// json
string serializeType = 1;
repeated bytes args = 2;
repeated string argTypes = 3;
}
message TripleResponseWrapper {
string serializeType = 1;
bytes data = 2;
string type = 3;
}
对于请求,使用 TripleRequestWrapper
进行包装,对于响应,使用 TripleResponseWrapper
进行包装。
对于请求参数,可以看到 args 被
repeated
修饰,这是因为需要支持 Java 方法的多个参数。当然,序列化只能是一个。序列化的实现遵循 Dubbo2 实现的 spi
多语言用户(使用 Protobuf)
建议所有新服务都使用此方法
对于 Dubbo3 和 Triple,主要建议是使用 protobuf
序列化,并使用 proto
定义的 IDL
生成相关的接口定义。使用 IDL
作为多种语言的通用接口约定,再加上 Triple
和 Grpc
的天然互操作性,可以轻松实现跨语言交互,例如 Go 语言。
使用 dubbo-compiler
插件编译准备好的 .proto
文件并编写实现类以完成方法调用
从上面的升级示例中,我们可以知道 Triple
协议使用 protobuf
对象进行序列化以进行传输,因此对于本身就是 protobuf
对象的方法没有其他逻辑。
使用 protobuf
插件编译后的接口如下
public interface PbGreeter {
static final String JAVA_SERVICE_NAME = "org.apache.dubbo.sample.tri.PbGreeter";
static final String SERVICE_NAME = "org.apache.dubbo.sample.tri.PbGreeter";
static final boolean inited = PbGreeterDubbo.init();
org.apache.dubbo.sample.tri.GreeterReply greet(org.apache.dubbo.sample.tri.GreeterRequest request);
default CompletableFuture<org.apache.dubbo.sample.tri.GreeterReply> greetAsync(org.apache.dubbo.sample.tri.GreeterRequest request){
return CompletableFuture. supplyAsync(() -> greet(request));
}
void greetServerStream(org.apache.dubbo.sample.tri.GreeterRequest request, org.apache.dubbo.common.stream.StreamObserver<org.apache.dubbo.sample.tri.GreeterReply> responseObserver);
org.apache.dubbo.common.stream.StreamObserver<org.apache.dubbo.sample.tri.GreeterRequest> greetStream(org.apache.dubbo.common.stream.StreamObserver<org.apache.dubbo.sample.tri.GreeterReply> responseObserver);
}
开启 Triple 的新特性 - 流(stream)
流是 Dubbo3 提供的一种新的调用类型。建议在以下场景中使用流
- 接口需要发送大量数据。这些数据无法放在一个 RPC 请求或响应中,需要分批发送。但是,如果应用层无法解决传统多 RPC 方法中的顺序和性能问题,如果需要保证顺序,则只能串行发送
- 在流式场景中,需要按照数据发送的顺序处理数据,并且数据本身没有明确的边界
- 在推送场景中,在同一个调用的上下文中发送和处理多条消息
流分为以下三种类型
- SERVER_STREAM(服务器流)
- CLIENT_STREAM(客户端流)
- BIDIRECTIONAL_STREAM(双向流)
由于
java
语言的限制,BIDIRECTIONAL_STREAM 和 CLIENT_STREAM 的实现是相同的。
在 Dubbo3 中,流接口被声明并用作 SteamObserver
,用户可以使用并实现此接口来发送和处理流数据、异常和结束。
对于 Dubbo2 用户来说,他们可能不熟悉 StreamObserver,它是 Dubbo3 定义的一种流类型。Dubbo2 中没有 Stream 类型,因此它对迁移场景没有影响。
流语义保证
- 提供消息边界,可以轻松地分别处理消息
- 严格排序,发送方的顺序与接收方的顺序一致
- 全双工,无需等待发送
- 支持取消和超时
非 PB 序列化流
- api
public interface IWrapperGreeter {
StreamObserver<String> sayHelloStream(StreamObserver<String> response);
void sayHelloServerStream(String request, StreamObserver<String> response);
}
Stream 方法的方法输入参数和返回值有严格的约定。为了防止写入错误导致的问题,Dubbo3 框架侧会检查参数,如果有错误则抛出异常。对于
BIDIRECTIONAL_STREAM
,需要注意的是参数中的StreamObserver
是响应流,返回值中的StreamObserver
是请求流。
- 实现类
public class WrapGreeterImpl implements WrapGreeter {
//...
@Override
public StreamObserver<String> sayHelloStream(StreamObserver<String> response) {
return new StreamObserver<String>() {
@Override
public void onNext(String data) {
System.out.println(data);
response.onNext("hello,"+data);
}
@Override
public void onError(Throwable throwable) {
throwable. printStackTrace();
}
@Override
public void onCompleted() {
System.out.println("onCompleted");
response.onCompleted();
}
};
}
@Override
public void sayHelloServerStream(String request, StreamObserver<String> response) {
for (int i = 0; i < 10; i++) {
response.onNext("hello," + request);
}
response.onCompleted();
}
}
- 调用方法
delegate.sayHelloServerStream("server stream", new StreamObserver<String>() {
@Override
public void onNext(String data) {
System.out.println(data);
}
@Override
public void onError(Throwable throwable) {
throwable. printStackTrace();
}
@Override
public void onCompleted() {
System.out.println("onCompleted");
}
});
StreamObserver<String> request = delegate.sayHelloStream(new StreamObserver<String>() {
@Override
public void onNext(String data) {
System.out.println(data);
}
@Override
public void onError(Throwable throwable) {
throwable. printStackTrace();
}
@Override
public void onCompleted() {
System.out.println("onCompleted");
}
});
for (int i = 0; i < n; i++) {
request.onNext("stream request" + i);
}
request.onCompleted();
使用 Protobuf 序列化的流
对于 Protobuf
序列化方法,建议编写 IDL
并使用 compiler
插件进行编译和生成。生成的代码大致如下
public interface PbGreeter {
static final String JAVA_SERVICE_NAME = "org.apache.dubbo.sample.tri.PbGreeter";
static final String SERVICE_NAME = "org.apache.dubbo.sample.tri.PbGreeter";
static final boolean inited = PbGreeterDubbo.init();
//...
void greetServerStream(org.apache.dubbo.sample.tri.GreeterRequest request, org.apache.dubbo.common.stream.StreamObserver<org.apache.dubbo.sample.tri.GreeterReply> responseObserver);
org.apache.dubbo.common.stream.StreamObserver<org.apache.dubbo.sample.tri.GreeterRequest> greetStream(org.apache.dubbo.common.stream.StreamObserver<org.apache.dubbo.sample.tri.GreeterReply> responseObserver);
}
流的实现原理
Triple
协议的流模式是如何支持的?
从协议层的角度来看,
Triple
是建立在HTTP2
的基础之上的,所以它直接拥有HTTP2
的所有能力,所以它具有拆分stream
和全双工的能力。在框架层,
StreamObserver
作为流接口提供给用户,为输入和输出参数提供流处理。框架在发送和接收流数据时进行相应的接口调用,从而保证流的生命周期完整性。
Triple 和应用级注册发现
Triple 协议的应用级服务注册和发现与其他语言一致,您可以通过以下内容了解更多信息。
与 GRPC 互通
通过对协议的介绍,我们知道 Triple
协议是基于 HTTP2
并兼容 GRPC
的。为了确保和验证与 GRPC
的互操作性,Dubbo3 还编写了各种从属场景下的测试。有关详细信息,您可以通过此处了解更多信息。
未来:一切都在 Stub 上
使用过 Grpc
的同学应该对 Stub
很熟悉。Grpc 使用 compiler
将编写的 proto
文件编译成相关的 protobuf 对象和相关的 rpc 接口。默认情况下,将同时生成多个不同的 stubs
-blockingStub
- futureStub -reactorStub -…
stub
帮助我们以统一的方式屏蔽了不同调用方法的细节。但是,目前 Dubbo3
只支持传统的定义和调用接口的方式。
在不久的将来,Triple
也将实现各种常用的 Stub
,让用户写一个 proto
文件,就可以通过 comipler
方便地在任何场景下使用,敬请期待。