Dubbo3 Triple協(xié)議遷移指南

2022-04-12 17:54 更新

Triple 介紹

Triple 協(xié)議的格式和原理請(qǐng)參閱 RPC 通信協(xié)議

根據(jù) Triple 設(shè)計(jì)的目標(biāo),?Triple ?協(xié)議有以下優(yōu)勢(shì):

  • 具備跨語(yǔ)言交互的能力,傳統(tǒng)的多語(yǔ)言多 SDK 模式和 Mesh 化跨語(yǔ)言模式都需要一種更通用易擴(kuò)展的數(shù)據(jù)傳輸協(xié)議。
  • 提供更完善的請(qǐng)求模型,除了支持傳統(tǒng)的 Request/Response 模型(Unary 單向通信),還支持 Stream(流式通信) 和 Bidirectional(雙向通信)。
  • 易擴(kuò)展、穿透性高,包括但不限于 Tracing / Monitoring 等支持,也應(yīng)該能被各層設(shè)備識(shí)別,網(wǎng)關(guān)設(shè)施等可以識(shí)別數(shù)據(jù)報(bào)文,對(duì) Service Mesh 部署友好,降低用戶理解難度。
  • 完全兼容 grpc,客戶端/服務(wù)端可以與原生grpc客戶端打通。
  • 可以復(fù)用現(xiàn)有 grpc 生態(tài)下的組件, 滿足云原生場(chǎng)景下的跨語(yǔ)言、跨環(huán)境、跨平臺(tái)的互通需求。

當(dāng)前使用其他協(xié)議的 Dubbo 用戶,框架提供了兼容現(xiàn)有序列化方式的遷移能力,在不影響線上已有業(yè)務(wù)的前提下,遷移協(xié)議的成本幾乎為零。

需要新增對(duì)接 Grpc 服務(wù)的 Dubbo 用戶,可以直接使用 Triple 協(xié)議來(lái)實(shí)現(xiàn)打通,不需要單獨(dú)引入 grpc client 來(lái)完成,不僅能保留已有的 Dubbo 易用性,也能降低程序的復(fù)雜度和開(kāi)發(fā)運(yùn)維成本,不需要額外進(jìn)行適配和開(kāi)發(fā)即可接入現(xiàn)有生態(tài)。

對(duì)于需要網(wǎng)關(guān)接入的 Dubbo 用戶,Triple 協(xié)議提供了更加原生的方式,讓網(wǎng)關(guān)開(kāi)發(fā)或者使用開(kāi)源的 grpc 網(wǎng)關(guān)組件更加簡(jiǎn)單。網(wǎng)關(guān)可以選擇不解析 payload ,在性能上也有很大提高。在使用 Dubbo 協(xié)議時(shí),語(yǔ)言相關(guān)的序列化方式是網(wǎng)關(guān)的一個(gè)很大痛點(diǎn),而傳統(tǒng)的 HTTP 轉(zhuǎn) Dubbo 的方式對(duì)于跨語(yǔ)言序列化幾乎是無(wú)能為力的。同時(shí),由于 Triple 的協(xié)議元數(shù)據(jù)都存儲(chǔ)在請(qǐng)求頭中,網(wǎng)關(guān)可以輕松的實(shí)現(xiàn)定制需求,如路由和限流等功能。

Dubbo2 協(xié)議遷移流程

Dubbo2 的用戶使用 dubbo 協(xié)議 + 自定義序列化,如 hessian2 完成遠(yuǎn)程調(diào)用。

而 Grpc 的默認(rèn)僅支持 Protobuf 序列化,對(duì)于 Java 語(yǔ)言中的多參數(shù)以及方法重載也無(wú)法支持。

Dubbo3的之初就有一條目標(biāo)是完美兼容 Dubbo2,所以為了 Dubbo2 能夠平滑升級(jí), Dubbo 框架側(cè)做了很多工作來(lái)保證升級(jí)的無(wú)感,目前默認(rèn)的序列化和 Dubbo2 保持一致為?hessian2?。

所以,如果決定要升級(jí)到 Dubbo3 的 Triple 協(xié)議,只需要修改配置中的協(xié)議名稱為 tri (注意: 不是triple)即可。

接下來(lái)我們我們以一個(gè)使用 Dubbo2 協(xié)議的工程 來(lái)舉例,如何一步一步安全的升級(jí)。

  1. 僅使用 ?dubbo ?協(xié)議啟動(dòng) ?provider ?和 ?consumer?,并完成調(diào)用。
  2. 使用 ?dubbo ?和 ?tri ?協(xié)議 啟動(dòng)?provider?,以 ?dubbo? 協(xié)議啟動(dòng) ?consumer?,并完成調(diào)用。
  3. 僅使用 ?tri ?協(xié)議 啟動(dòng) ?provider?和 ?consumer?,并完成調(diào)用。

定義服務(wù)

  1. 定義接口
public interface IWrapperGreeter {

    //... 
    
    /**
     * 這是一個(gè)普通接口,沒(méi)有使用 pb 序列化
     */
    String sayHello(String request);

}
  1. 實(shí)現(xiàn)類如下
public class IGreeter2Impl implements IWrapperGreeter {

    @Override
    public String sayHello(String request) {
        return "hello," + request;
    }
}

僅使用 dubbo 協(xié)議

為保證兼容性,我們先將部分 provider 升級(jí)到 ?dubbo3 ?版本并使用 ?dubbo ?協(xié)議。

使用 dubbo 協(xié)議啟動(dòng)一個(gè) Provider 和 Consumer ,完成調(diào)用,輸出如下:result

同時(shí)使用 dubbo 和 triple 協(xié)議

對(duì)于線上服務(wù)的升級(jí),不可能一蹴而就同時(shí)完成 provider 和 consumer 升級(jí), 需要按步操作,保證業(yè)務(wù)穩(wěn)定。 第二步, provider 提供雙協(xié)議的方式同時(shí)支持 dubbo + tri 兩種協(xié)議的客戶端。

結(jié)構(gòu)如圖所示:  strust

按照推薦升級(jí)步驟,provider 已經(jīng)支持了tri協(xié)議,所以 dubbo3的 consumer 可以直接使用 tri 協(xié)議

使用dubbo協(xié)議和triple協(xié)議啟動(dòng)ProviderConsumer,完成調(diào)用,輸出如下:

result

僅使用 triple 協(xié)議

當(dāng)所有的 consuemr 都升級(jí)至支持 ?Triple ?協(xié)議的版本后,?provider ?可切換至僅使用 ?Triple ?協(xié)議啟動(dòng)

結(jié)構(gòu)如圖所示:  strust

Provider 和 Consumer 完成調(diào)用,輸出如下:


result

實(shí)現(xiàn)原理

通過(guò)上面介紹的升級(jí)過(guò)程,我們可以很簡(jiǎn)單的通過(guò)修改協(xié)議類型來(lái)完成升級(jí)??蚣苁窃趺磶臀覀冏龅竭@些的呢?

通過(guò)對(duì) Triple 協(xié)議的介紹,我們知道Dubbo3的 Triple 的數(shù)據(jù)類型是 protobuf 對(duì)象,那為什么非 protobuf 的 java 對(duì)象也可以被正常傳輸呢。

這里 Dubbo3 使用了一個(gè)巧妙的設(shè)計(jì),首先判斷參數(shù)類型是否為 protobuf 對(duì)象,如果不是。用一個(gè) protobuf 對(duì)象將 request 和 response 進(jìn)行 wrapper,這樣就屏蔽了其他各種序列化帶來(lái)的復(fù)雜度。在 wrapper 對(duì)象內(nèi)部聲明序列化類型,來(lái)支持序列化的擴(kuò)展。

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;
}

對(duì)于請(qǐng)求,使用TripleRequestWrapper進(jìn)行包裝,對(duì)于響應(yīng)使用TripleResponseWrapper進(jìn)行包裝。

對(duì)于請(qǐng)求參數(shù),可以看到 args 被repeated修飾,這是因?yàn)樾枰С?java 方法的多個(gè)參數(shù)。當(dāng)然,序列化只能是一種。序列化的實(shí)現(xiàn)沿用 Dubbo2 實(shí)現(xiàn)的 spi

多語(yǔ)言用戶 (正在使用 Protobuf)

建議新服務(wù)均使用該方式

對(duì)于 Dubbo3 和 Triple 來(lái)說(shuō),主推的是使用 protobuf 序列化,并且使用 proto 定義的 IDL 來(lái)生成相關(guān)接口定義。以 IDL 做為多語(yǔ)言中的通用接口約定,加上 Triple 與 Grpc 的天然互通性,可以輕松地實(shí)現(xiàn)跨語(yǔ)言交互,例如 Go 語(yǔ)言等。

將編寫(xiě)好的 .proto 文件使用 dubbo-compiler 插件進(jìn)行編譯并編寫(xiě)實(shí)現(xiàn)類,完成方法調(diào)用:

result

從上面升級(jí)的例子我們可以知道,Triple 協(xié)議使用 protbuf 對(duì)象序列化后進(jìn)行傳輸,所以對(duì)于本身就是 protobuf 對(duì)象的方法來(lái)說(shuō),沒(méi)有任何其他邏輯。

使用 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);
}

開(kāi)啟 Triple 新特性 —— Stream (流)

Stream 是 Dubbo3 新提供的一種調(diào)用類型,在以下場(chǎng)景時(shí)建議使用流的方式:

  • 接口需要發(fā)送大量數(shù)據(jù),這些數(shù)據(jù)無(wú)法被放在一個(gè) RPC 的請(qǐng)求或響應(yīng)中,需要分批發(fā)送,但應(yīng)用層如果按照傳統(tǒng)的多次 RPC 方式無(wú)法解決順序和性能的問(wèn)題,如果需要保證有序,則只能串行發(fā)送
  • 流式場(chǎng)景,數(shù)據(jù)需要按照發(fā)送順序處理, 數(shù)據(jù)本身是沒(méi)有確定邊界的
  • 推送類場(chǎng)景,多個(gè)消息在同一個(gè)調(diào)用的上下文中被發(fā)送和處理

Stream 分為以下三種:

  • SERVER_STREAM(服務(wù)端流)  SERVER_STREAM
  • CLIENT_STREAM(客戶端流)  CLIENT_STREAM
  • BIDIRECTIONAL_STREAM(雙向流)  BIDIRECTIONAL_STREAM
由于 java 語(yǔ)言的限制,BIDIRECTIONAL_STREAM 和 CLIENT_STREAM 的實(shí)現(xiàn)是一樣的。

在 Dubbo3 中,流式接口以 SteamObserver 聲明和使用,用戶可以通過(guò)使用和實(shí)現(xiàn)這個(gè)接口來(lái)發(fā)送和處理流的數(shù)據(jù)、異常和結(jié)束。

對(duì)于 Dubbo2 用戶來(lái)說(shuō),可能會(huì)對(duì)StreamObserver感到陌生,這是Dubbo3定義的一種流類型,Dubbo2 中并不存在 Stream 的類型,所以對(duì)于遷移場(chǎng)景沒(méi)有任何影響。

流的語(yǔ)義保證

  • 提供消息邊界,可以方便地對(duì)消息單獨(dú)處理
  • 嚴(yán)格有序,發(fā)送端的順序和接收端順序一致
  • 全雙工,發(fā)送不需要等待
  • 支持取消和超時(shí)

非 PB 序列化的流

  1. api
public interface IWrapperGreeter {

    StreamObserver<String> sayHelloStream(StreamObserver<String> response);

    void sayHelloServerStream(String request, StreamObserver<String> response);
}
Stream 方法的方法入?yún)⒑头祷刂凳菄?yán)格約定的,為防止寫(xiě)錯(cuò)而導(dǎo)致問(wèn)題,Dubbo3 框架側(cè)做了對(duì)參數(shù)的檢查, 如果出錯(cuò)則會(huì)拋出異常。 對(duì)于 雙向流(BIDIRECTIONAL_STREAM), 需要注意參數(shù)中的 StreamObserver 是響應(yīng)流,返回參數(shù)中的 StreamObserver 為請(qǐng)求流。
  1. 實(shí)現(xiàn)類
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();
    }
}
  1. 調(diào)用方式
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 序列化的流

對(duì)于 Protobuf 序列化方式,推薦編寫(xiě) IDL 使用 compiler 插件進(jìn)行編譯生成。生成的代碼大致如下:

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);
}

流的實(shí)現(xiàn)原理

Triple協(xié)議的流模式是怎么支持的呢?

  • 從協(xié)議層來(lái)說(shuō),Triple 是建立在 HTTP2 基礎(chǔ)上的,所以直接擁有所有 HTTP2 的能力,故擁有了分 stream 和全雙工的能力。
  • 框架層來(lái)說(shuō),StreamObserver 作為流的接口提供給用戶,用于入?yún)⒑统鰠⑻峁┝魇教幚怼?蚣茉谑瞻l(fā) stream data 時(shí)進(jìn)行相應(yīng)的接口調(diào)用, 從而保證流的生命周期完整。

Triple 與應(yīng)用級(jí)注冊(cè)發(fā)現(xiàn)

關(guān)于 Triple 協(xié)議的應(yīng)用級(jí)服務(wù)注冊(cè)和發(fā)現(xiàn)和其他語(yǔ)言是一致的,可以通過(guò)下列內(nèi)容了解更多。

與 GRPC 互通

通過(guò)對(duì)于協(xié)議的介紹,我們知道 Triple 協(xié)議是基于 HTTP2 并兼容 GRPC。為了保證和驗(yàn)證與GRPC互通能力,Dubbo3 也編寫(xiě)了各種從場(chǎng)景下的測(cè)試。詳細(xì)的可以通過(guò)這里 了解更多。

未來(lái): Everything on Stub

用過(guò) Grpc 的同學(xué)應(yīng)該對(duì) Stub 都不陌生。 Grpc 使用 compiler 將編寫(xiě)的 proto 文件編譯為相關(guān)的 protobuf 對(duì)象和相關(guān) rpc 接口。默認(rèn)的會(huì)同時(shí)生成幾種不同的 stub

  • blockingStub
  • futureStub
  • reactorStub

stub 用一種統(tǒng)一的使用方式幫我們屏蔽了不同調(diào)用方式的細(xì)節(jié)。不過(guò)目前 Dubbo3 暫時(shí)只支持傳統(tǒng)定義接口并進(jìn)行調(diào)用的使用方式。

在不久的未來(lái),Triple 也將實(shí)現(xiàn)各種常用的 Stub,讓用戶寫(xiě)一份proto文件,通過(guò) comipler 可以在任意場(chǎng)景方便的使用,請(qǐng)拭目以待。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)