此文档已经不再维护。您当前查看的是快照版本。如果想要查看最新版本的文档,请参阅最新版本。
Triple
协议的格式和原理请参阅 RPC 通信协议
根据 Triple 设计的目标,Triple
协议有以下优势:
当前使用其他协议的 Dubbo 用户,框架提供了兼容现有序列化方式的迁移能力,在不影响线上已有业务的前提下,迁移协议的成本几乎为零。
需要新增对接 Grpc 服务的 Dubbo 用户,可以直接使用 Triple 协议来实现打通,不需要单独引入 grpc client 来完成,不仅能保留已有的 Dubbo 易用性,也能降低程序的复杂度和开发运维成本,不需要额外进行适配和开发即可接入现有生态。
对于需要网关接入的 Dubbo 用户,Triple 协议提供了更加原生的方式,让网关开发或者使用开源的 grpc 网关组件更加简单。网关可以选择不解析 payload ,在性能上也有很大提高。在使用 Dubbo 协议时,语言相关的序列化方式是网关的一个很大痛点,而传统的 HTTP 转 Dubbo 的方式对于跨语言序列化几乎是无能为力的。同时,由于 Triple 的协议元数据都存储在请求头中,网关可以轻松的实现定制需求,如路由和限流等功能。
Dubbo2 的用户使用 dubbo 协议 + 自定义序列化,如 hessian2 完成远程调用。
而 Grpc 的默认仅支持 Protobuf 序列化,对于 Java 语言中的多参数以及方法重载也无法支持。
Dubbo3的之初就有一条目标是完美兼容 Dubbo2,所以为了 Dubbo2 能够平滑升级, Dubbo 框架侧做了很多工作来保证升级的无感,目前默认的序列化和 Dubbo2 保持一致为hessian2
。
所以,如果决定要升级到 Dubbo3 的 Triple
协议,只需要修改配置中的协议名称为 tri
(注意: 不是triple)即可。
接下来我们我们以一个使用 Dubbo2 协议的工程 来举例,如何一步一步安全的升级。
dubbo
协议启动 provider
和 consumer
,并完成调用。dubbo
和 tri
协议 启动provider
,以 dubbo
协议启动 consumer
,并完成调用。tri
协议 启动 provider
和 consumer
,并完成调用。public interface IWrapperGreeter {
//...
/**
* 这是一个普通接口,没有使用 pb 序列化
*/
String sayHello(String request);
}
public class IGreeter2Impl implements IWrapperGreeter {
@Override
public String sayHello(String request) {
return "hello," + request;
}
}
为保证兼容性,我们先将部分 provider 升级到 dubbo3
版本并使用 dubbo
协议。
使用 dubbo
协议启动一个 Provider
和 Consumer
,完成调用,输出如下:
对于线上服务的升级,不可能一蹴而就同时完成 provider 和 consumer 升级, 需要按步操作,保证业务稳定。 第二步, provider 提供双协议的方式同时支持 dubbo + tri 两种协议的客户端。
结构如图所示:
按照推荐升级步骤,provider 已经支持了tri协议,所以 dubbo3的 consumer 可以直接使用 tri 协议
使用dubbo
协议和triple
协议启动Provider
和Consumer
,完成调用,输出如下:
当所有的 consuemr 都升级至支持 Triple
协议的版本后,provider 可切换至仅使用 Triple
协议启动
结构如图所示:
Provider 和 Consumer 完成调用,输出如下:
通过上面介绍的升级过程,我们可以很简单的通过修改协议类型来完成升级。框架是怎么帮我们做到这些的呢?
通过对 Triple
协议的介绍,我们知道Dubbo3的 Triple
的数据类型是 protobuf
对象,那为什么非 protobuf
的 java 对象也可以被正常传输呢。
这里 Dubbo3 使用了一个巧妙的设计,首先判断参数类型是否为 protobuf
对象,如果不是。用一个 protobuf
对象将 request
和 response
进行 wrapper,这样就屏蔽了其他各种序列化带来的复杂度。在 wrapper
对象内部声明序列化类型,来支持序列化的扩展。
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
建议新服务均使用该方式
对于 Dubbo3 和 Triple 来说,主推的是使用 protobuf
序列化,并且使用 proto
定义的 IDL
来生成相关接口定义。以 IDL
做为多语言中的通用接口约定,加上 Triple
与 Grpc
的天然互通性,可以轻松地实现跨语言交互,例如 Go 语言等。
将编写好的 .proto
文件使用 dubbo-compiler
插件进行编译并编写实现类,完成方法调用:
从上面升级的例子我们可以知道,Triple
协议使用 protbuf
对象序列化后进行传输,所以对于本身就是 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);
}
Stream 是 Dubbo3 新提供的一种调用类型,在以下场景时建议使用流的方式:
Stream 分为以下三种:
由于
java
语言的限制,BIDIRECTIONAL_STREAM 和 CLIENT_STREAM 的实现是一样的。
在 Dubbo3 中,流式接口以 SteamObserver
声明和使用,用户可以通过使用和实现这个接口来发送和处理流的数据、异常和结束。
对于 Dubbo2 用户来说,可能会对StreamObserver感到陌生,这是Dubbo3定义的一种流类型,Dubbo2 中并不存在 Stream 的类型,所以对于迁移场景没有任何影响。
流的语义保证
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
序列化方式,推荐编写 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
作为流的接口提供给用户,用于入参和出参提供流式处理。框架在收发 stream data 时进行相应的接口调用, 从而保证流的生命周期完整。
关于 Triple 协议的应用级服务注册和发现和其他语言是一致的,可以通过下列内容了解更多。
通过对于协议的介绍,我们知道 Triple
协议是基于 HTTP2
并兼容 GRPC
。为了保证和验证与GRPC
互通能力,Dubbo3 也编写了各种从场景下的测试。详细的可以通过这里 了解更多。
用过 Grpc
的同学应该对 Stub
都不陌生。
Grpc 使用 compiler
将编写的 proto
文件编译为相关的 protobuf 对象和相关 rpc 接口。默认的会同时生成几种不同的 stub
stub
用一种统一的使用方式帮我们屏蔽了不同调用方式的细节。不过目前 Dubbo3
暂时只支持传统定义接口并进行调用的使用方式。
在不久的未来,Triple
也将实现各种常用的 Stub
,让用户写一份proto
文件,通过 comipler
可以在任意场景方便的使用,请拭目以待。