Dubbo3 提供了 Triple(Dubbo3)、Dubbo2 協定,這是 Dubbo 架構的原生協定。除此之外,Dubbo3 也對衆多第三方協定進行了內建,并将它們納入 Dubbo 的程式設計與服務治理體系, 包括 gRPC、Thrift、JsonRPC、Hessian2、REST 等。以下重點介紹 Triple 與 Dubbo2 協定。
下一代 RPC 協定 - Triple
Triple 協定是 Dubbo3 推出的主力協定。Triple 意為第三代,通過 Dubbo1.0/ Dubbo2.0 兩代協定的演進,以及雲原生帶來的技術标準化浪潮,Dubbo3 新協定 Triple 應運而生。
RPC 協定簡介
協定是 RPC 的核心,它規範了資料在網絡中的傳輸内容和格式。除必須的請求、響應資料外,通常還會包含額外控制資料,如單次請求的序列化方式、逾時時間、壓縮方式和鑒權資訊等。
協定的内容包含三部分:
- 資料交換格式:定義 RPC 的請求和響應對象在網絡傳輸中的位元組流内容,也叫作序列化方式;
- 協定結構:定義包含字段清單和各字段語義以及不同字段的排列方式;
- 協定通過定義規則、格式和語義來約定資料如何在網絡間傳輸。一次成功的 RPC 需要通信的兩端都能夠按照協定約定進行網絡位元組流的讀寫和對象轉換。如果兩端對使用的協定不能達成一緻,就會出現雞同鴨講,無法滿足遠端通信的需求。
RPC 協定的設計需要考慮以下内容:
- 通用性:統一的二進制格式,跨語言、跨平台、多傳輸層協定支援
- 擴充性:協定增加字段、更新、支援使用者擴充和附加業務中繼資料
- 性能:As fast as it can be
- 穿透性:能夠被各種終端裝置識别和轉發:網關、代理伺服器等 通用性和高性能通常無法同時達到,需要協定設計者進行一定的取舍
HTTP/1.1
比于直接建構于 TCP 傳輸層的私有 RPC 協定,建構于 HTTP 之上的遠端調用解決方案會有更好的通用性,如WebServices 或 REST 架構,使用 HTTP + JSON 可以說是一個事實标準的解決方案。
選擇建構在 HTTP 之上,有兩個最大的優勢:
- HTTP 的語義和可擴充性能很好的滿足 RPC 調用需求。
- 通用性,HTTP 協定幾乎被網絡上的所有裝置所支援,具有很好的協定穿透性。
但也存在比較明顯的問題:
- 典型的 Request – Response 模型,一個鍊路上一次隻能有一個等待的 Request 請求。會産生 HOL。
- Human Readable Headers,使用更通用、更易于人類閱讀的頭部傳輸格式,但性能相當差
- 無直接 Server Push 支援,需要使用 Polling Long-Polling 等變通模式
gRPC
上面提到了在 HTTP 及 TCP 協定之上建構 RPC 協定各自的優缺點,相比于 Dubbo 建構于 TCP 傳輸層之上,Google 選擇将 gRPC 直接定義在 HTTP/2 協定之上。gRPC 的優勢由 HTTP2 和 Protobuf 繼承而來。
- 基于 HTTP2 的協定足夠簡單,使用者學習成本低,天然有 server push / 多路複用 / 流量控制能力
- 基于 Protobuf 的多語言跨平台二進制相容能力,提供強大的統一跨語言能力
- 基于協定本身的生态比較豐富,K8s / etcd 等元件的天然支援協定,雲原生的事實協定标準
但是也存在部分問題
- 對服務治理的支援比較基礎,更偏向于基礎的 RPC 功能,協定層缺少必要的統一定義,對于使用者而言直接用起來并不容易。
- 強綁定 protobuf 的序列化方式,需要較高的學習成本和改造成本,對于現有的偏單語言的使用者而言,遷移成本不可忽視
Triple 選型的思考
最終我們選擇了相容 gRPC ,以 HTTP2 作為傳輸層建構新的協定,也就是 Triple。容器化應用程式和微服務的興起促進了針對負載内容優化技術的發展。用戶端中使用的傳統通信協定( RESTFUL或其他基于 HTTP 的自定義協定)難以滿足應用在性能、可維護性、擴充性、安全性等友善的需求。一個跨語言、子產品化的協定會逐漸成為新的應用開發協定标準。自從 2017 年 gRPC 協定成為 CNCF 的項目後,包括 K8s、etcd 等越來越多的基礎設施和業務都開始使用 gRPC 的生态,作為雲原生的微服務化架構, Dubbo 的新協定也完美相容了 gRPC。并且,對于 gRPC 協定中一些不完善的部分, Triple 也将進行增強和補充。那麼,Triple 協定是否解決了上面我們提到的一系列問題呢?
- 性能上: Triple 協定采取了 metadata 和 payload 分離的政策,這樣就可以避免中間裝置,如網關進行 payload 的解析和反序列化,進而降低響應時間。
- 路由支援上,由于 metadata 支援使用者添加自定義 header ,使用者可以根據 header 更友善的劃分叢集或者進行路由,這樣釋出的時候切流灰階或容災都有了更高的靈活性。
- 安全性上,支援雙向 TLS 認證(mTLS)等加密傳輸能力。
- 易用性上,Triple 除了支援原生 gRPC 所推薦的 Protobuf 序列化外,使用通用的方式支援了 Hessian / JSON 等其他序列化,能讓使用者更友善的更新到 Triple 協定。對原有的 Dubbo 服務而言,修改或增加 Triple 協定 隻需要在聲明服務的代碼塊添加一行協定配置即可,改造成本幾乎為 0。
現狀
1、完整相容 gRPC、用戶端/服務端可以與原生 gRPC 用戶端打通
2、目前已經經過大規模生産實踐驗證,達到生産級别
特點與優勢
1、具備跨語言互通的能力,傳統的多語言多 SDK 模式和 Mesh 化跨語言模式都需要一種更通用易擴充的資料傳輸格式。
2、提供更完善的請求模型,除了 Request/Response 模型,還應該支援 Streaming 和 Bidirectional。
3、易擴充、穿透性高,包括但不限于 Tracing / Monitoring 等支援,也應該能被各層裝置識别,網關設施等可以識别資料封包,對 Service Mesh 部署友好,降低使用者了解難度。
4、多種序列化方式支援、平滑更新。
5、支援 Java 使用者無感覺更新,不需要定義繁瑣的 IDL 檔案,僅需要簡單的修改協定名便可以輕松更新到 Triple 協定。
Triple 協定簡介
基于 gRPC 協定進行進一步擴充
- Service-Version → “tri-service-version” {Dubbo service version}
- Service-Group → “tri-service-group” {Dubbo service group}
- Tracing-ID → “tri-trace-traceid” {tracing id}
- Tracing-RPC-ID → “tri-trace-rpcid” {_span id _}
- Cluster-Info → “tri-unit-info” {cluster infomation}
其中 Service-Version 跟 Service-Group 分别辨別了 Dubbo 服務的 version 跟 group 資訊,因為 grpc 的 path 申明了 service name 跟 method name,相比于 Dubbo 協定,缺少了 version 跟 group 資訊;Tracing-ID、Tracing-RPC-ID 用于全鍊路追蹤能力,分别表示 tracing id 跟 span id 資訊;Cluster-Info 表示叢集資訊,可以使用其建構一些如叢集劃分等路由相關的靈活的服務治理能力。
Triple Streaming
Triple 協定相比傳統的 unary 方式,多了目前提供的 Streaming RPC 的能力
- Streaming 用于什麼場景呢?
在一些大檔案傳輸、直播等應用場景中, consumer 或 provider 需要跟對端進行大量資料的傳輸,由于這些情況下的資料量是非常大的,是以是沒有辦法可以在一個 RPC 的資料包中進行傳輸,是以對于這些資料包我們需要對資料包進行分片之後,通過多次 RPC 調用進行傳輸,如果我們對這些已經拆分了的 RPC 資料包進行并行傳輸,那麼到對端後相關的資料包是無序的,需要對接收到的資料進行排序拼接,相關的邏輯會非常複雜。但如果我們對拆分了的 RPC 資料包進行串行傳輸,那麼對應的網絡傳輸 RTT 與資料處理的時延會是非常大的。
為了解決以上的問題,并且為了大量資料的傳輸以流水線方式在 consumer 與 provider 之間傳輸,是以 Streaming RPC 的模型應運而生。
通過 Triple 協定的 Streaming RPC 方式,會在 consumer 跟 provider 之間建立多條使用者态的長連接配接,Stream。同一個 TCP 連接配接之上能同時存在多個 Stream,其中每條 Stream 都有 StreamId 進行辨別,對于一條 Stream 上的資料包會以順序方式讀寫。
總結
在 API 領域,最重要的趨勢是标準化技術的崛起。Triple 協定是 Dubbo3 推出的主力協定。它采用分層設計,其資料交換格式基于 Protobuf (Protocol Buffers) 協定開發,具備優秀的序列化/反序列化效率,當然還支援多種序列化方式,也支援衆多開發語言。在傳輸層協定,Triple 選擇了 HTTP/2,相較于 HTTP/1.1,其傳輸效率有了很大提升。此外 HTTP/2 作為一個成熟的開放标準,具備豐富的安全、流控等能力,同時擁有良好的互操作性。Triple 不僅可以用于 Server 端服務調用,也可以支援浏覽器、移動 App 和 IoT 裝置與後端服務的互動,同時 Triple協定無縫支援 Dubbo3 的全部服務治理能力。
在 Cloud Native 的潮流下,跨平台、跨廠商、跨環境的系統間互操作性的需求必然會催生基于開放标準的 RPC 技術,而 gRPC 順應了曆史趨勢,得到了越來越廣泛地應用。在微服務領域,Triple 協定的提出與落地,是 Dubbo3 邁向雲原生微服務的一大步。
附錄:Dubbo2 Protocol SPEC
Protocol SPEC
- Magic - Magic High & Magic Low (16 bits)Identifies dubbo protocol with value: 0xdabb
- Req/Res (1 bit)Identifies this is a request or response. Request - 1; Response - 0.
- 2 Way (1 bit)Only useful when Req/Res is 1 (Request), expect for a return value from server or not. Set to 1 if need a return value from server.
- Event (1 bit)Identifies an event message or not, for example, heartbeat event. Set to 1 if this is an event.
- Serialization ID (5 bit)Identifies serialization type: the value for fastjson is 6.
- Status (8 bits)Only useful when Req/Res is 0 (Response), identifies the status of response
-
- 20 - OK
- 30 - CLIENT_TIMEOUT
- 31 - SERVER_TIMEOUT
- 40 - BAD_REQUEST
- 50 - BAD_RESPONSE
- 60 - SERVICE_NOT_FOUND
- 70 - SERVICE_ERROR
- 80 - SERVER_ERROR
- 90 - CLIENT_ERROR
- 100 - SERVER_THREADPOOL_EXHAUSTED_ERROR
- Request ID (64 bits)Identifies an unique request. Numeric (long).
- Data Length (32)Length of the content (the variable part) after serialization, counted by bytes. Numeric (integer).
- Variable PartEach part is a byte[] after serialization with specific serialization type, identifies by Serialization ID.
Every part is a byte[] after serialization with specific serialization type, identifies by Serialization ID
- If the content is a Request (Req/Res = 1), each part consists of the content, in turn is:
-
- Dubbo version
- Service name
- Service version
- Method name
- Method parameter types
- Method arguments
- Attachments
- If the content is a Response (Req/Res = 0), each part consists of the content, in turn is:
-
- Return value type, identifies what kind of value returns from server side: RESPONSE_NULL_VALUE - 2, RESPONSE_VALUE - 1, RESPONSE_WITH_EXCEPTION - 0.
- Return value, the real value returns from server.
注意: 對于 (Variable Part) 變長部分,目前版本的 dubbo 架構使用 json 序列化時,在每部分内容間額外增加了換行符作為分隔,請選手在 Variable Part 的每個 part 後額外增加換行符, 如:
Dubbo version bytes (換行符)
Service name bytes (換行符)
...