里程碑式 Dubbo 2.7.5 版本發佈,性能提升30%,支持 HTTP

作者簡介:劉軍,GitHub賬號Chickenlj,Apache Dubbo PMC,項目核心維護者,見證了Dubbo從重啟開源到Apache畢業的整個流程。現任職阿里云云原生應用平臺團隊,參與服務框架、微服務相關工作,目前主要在推動Dubbo開源的雲原生化。

近日,備受矚目的 Apache Dubbo(以下簡稱 Dubbo)2.7.5 版本正式發佈,在 2.7.5 版本中,Dubbo 引入了很多新的特性、對現有的很多功能做了增強、同時在性能上也有了非常大的提升,這個版本無論對 Dubbo 社區亦或是開發者來說,都將是一個里程碑式的版本。

應用粒度服務註冊【beta】

HTTP/2 (gRPC) 協議支持

Protobuf 支持

性能優化,調用鏈路性能提升 30%

支持 TLS 安全傳輸鏈路

優化的消費端線程模型

新增更適應多集群部署場景的負載均衡策略

全新的應用開發 API (兼容老版本應用)【beta】

其他一些功能增強與 bugfix

首先,從服務發現上,新版本突破以往基於接口粒度的模型,引入了全新的基於應用粒度的服務發現機制 - 服務自省,雖然該機制當前仍處於 beta 階段,但對於 Dubbo 向整個微服務雲原生體系靠齊,都打下了非常好的基礎;得益於緊湊的協議設計和代碼實現上的優化,Dubbo 一直以來都具有較好的性能表現,在 2.7.5 版本中,性能上有了進一步的提升,根據來自官方維護團隊的壓測,新版本在調用鏈路上性能提升達到 30%;雲原生微服務時代,多語言需求變得越來越普遍,協議的通用性和穿透性對於構建打通前後端的整套微服務體系也變得非常關鍵,Dubbo 通過實現 gRPC 協議實現了對 HTTP/2 協議的支持,同時增加了與 Protobuf 的結合。

1. 應用粒度服務註冊【beta】

從 Java 實現版本的角度來說,Dubbo 是一個面向接口代理的服務開發框架,服務定義、服務發佈以及服務引用都是基於接口,服務治理層面包括服務發現、各種規則定義也都是基於接口定義的,基於接口可以說是 Dubbo 的一大優勢,比如向開發者屏蔽了遠程調用細節、治理粒度更精細等。但基於接口的服務定義同時也存在一些問題,如服務,與業界通用的微服務體系等。

針對以上問題,2.7.5 版本引入了一種新的服務定義/治理機制:服務自省,簡單來說這是一種基於應用粒度的服務治理方案。一個實例只向註冊中心註冊一條記錄,徹底解決服務推送性能瓶頸,同時由於這樣的模型與主流微服務體系如 SpringCloud、K8S 等天然是對等的,因此為 Dubbo 解決和此類異構體系間的互聯互通清除了障礙。有興趣進一步瞭解 Dubbo 服務自省機制如何解決異構微服務體系互聯互通問題的,可具體參考我們之前的文章解析《Dubbo 如何成為聯通異構微服務體系的最佳服務開發框架》。

以下是服務自省機制的基本工作原理圖。

要了解更多關於服務自省工作原理的細節,請參與官方文檔及後續文章。

服務自省與當前已有的機制之間可以說是互補的關係,Dubbo 框架會繼續保持接口粒度的服務治理的優勢,實現接口和應用兩個粒度互為補充的局面,兼顧性能、靈活性和通用性,力爭使 Dubbo 成為微服務開發的最佳框架。

2. HTTP/2 (gRPC) 協議支持

Dubbo RPC 協議是構建在 TCP 之上,這有很多優勢也有一些缺點,缺點比如通用性、協議穿透性不強,對多語言實現不夠友好等。HTTP/2 由於其標準 HTTP 協議的屬性,無疑將具有更好的通用性,現在或將來在各層網絡設備上肯定都會得到很好的支持,gRPC 之所以選在 HTTP/2 作為傳輸層載體很大程度上也是因為這個因素。當前 gRPC 在雲原生、Mesh 等體系下的認可度和採用度逐步提升,儼然有成為 RPC 協議傳輸標準的趨勢,Dubbo 和 gRPC 在協議層面是對等競爭的,但是在框架實現上卻各有側重,Dubbo 無疑有更豐富的服務開發和治理體驗 。

Dubbo 支持 gRPC 協議帶來的直觀好處有:

正式支持基於 HTTP/2 的遠程通信,在協議通用性和穿透性上進一步提升。

支持跨進程的 Stream 流式通信,支持 Reactive 風格的 RPC 編程。

解決了 gRPC 框架難以直接用於微服務開發的問題,將其納入 Dubbo 的服務治理體系。

為聯通組織內部已有的 gRPC 或多語言體系提供支持。

2.7.5 版本開始,gRPC (HTTP/2) 成為 Dubbo 協議體系中的一等公民,對於有需求的開發者完全可以在 Dubbo 開發的微服務體系中啟用 gRPC 協議,而不必束縛在 Dubbo 協議自身上,關於這點我們在《Dubbo 如何成為聯通異構微服務體系的最佳服務開發框架》一文中也有類似的觀點表述。

關於 Dubbo 中如何開發 grpc (HTTP/2) 服務的細節,請參考文章《Dubbo 在跨語言與協議穿透性等方面的探索》,關於如何開啟 TLS 和使用 Reactive RPC 編程,請參見示例

https://github.com/apache/dubbo-samples/tree/master/java/dubbo-samples-ssl 及 https://github.com/apache/dubbo-samples/tree/master/java/dubbo-samples-grpc 。另外,Dubbo 的 go 版本目前同樣也提供了對 gRPC 協議對等的支持,具體請關注 dubbogo 社區的發版計劃。

3. Protobuf 支持

支持 Protobuf 更多的是從解決 Dubbo 跨語言易用性的角度考慮的。

跨語言的服務開發涉及到多個方面,從服務定義、RPC 協議到序列化協議都要做到語言中立,同時還針對每種語言有對應的 SDK 實現。雖然得益於社區的貢獻,現在 Dubbo 在多語言 SDK 實現上逐步有了起色,已經提供了包括 Java, Go, PHP, C#, Python, NodeJs, C 等版本的客戶端或全量實現版本,但在以上提到的跨語言友好性方面,以上三點還是有很多可改進之處。

協議上 2.7.5 版本支持了 gRPC,而關於服務定義與序列化,Protobuf 則提供了很好的解決方案。

服務定義。當前 Dubbo 的服務定義和具體的編程語言綁定,沒有提供一種語言中立的服務描述格式,比如 Java 就是定義 Interface 接口,到了其他語言又得重新以另外的格式定義一遍。因此 Dubbo 通過支持 Protobuf 實現了語言中立的服務定義。

序列化。Dubbo 當前支持的序列化包括 Json、Hessian2、Kryo、FST、Java 等,而這其中支持跨語言的只有 Json、Hessian2,通用的 Json 有固有的性能問題,而 Hessian2 無論在效率還是多語言 SDK 方面都有所欠缺。為此,Dubbo 通過支持 Protobuf 序列化來提供更高效、易用的跨語言序列化方案。

日後,不論我們使用什麼語言版本來開發 Dubbo 服務,都可以直接使用 IDL 定義如下服務,具體請參見示例

<code>syntax = "proto3";/<code>
<code>option java_multiple_files = true;/<code><code>option java_package = "org.apache.dubbo.demo";/<code><code>option java_outer_classname = "DemoServiceProto";/<code><code>option objc_class_prefix = "DEMOSRV";/<code>
<code>package demoservice;/<code>
<code>// The demo service definition./<code><code>service DemoService {/<code><code> rpc SayHello (HelloRequest) returns (HelloReply) {}/<code><code>}/<code>
<code>// The request message containing the user's name./<code><code>message HelloRequest {/<code><code> string name = 1;/<code><code>}/<code>
<code>// The response message containing the greetings/<code><code>message HelloReply {/<code><code> string message = 1;/<code><code>}/<code>

4. 性能優化

4.1 調用鏈路優化

2.7.5 版本對整個調用鏈路做了全面的優化,根據壓測結果顯示,總體 QPS 性能提升將近 30%,同時也減少了調用過程中的內存分配開銷。其中一個值得提及的設計點是 2.7.5 引入了 Servicerepository 的概念,在服務註冊階段提前生成 ServiceDescriptor 和 MethodDescriptor,以減少 RPC 調用階段計算 Service 原信息帶來的資源消耗。

4.2 消費端線程池模型優化

對 2.7.5 版本之前的 Dubbo 應用,尤其是一些消費端應用,當面臨需要消費大量服務且併發數比較大的大流量場景時(典型如網關類場景),經常會出現消費端線程數分配過多的問題,具體問題討論可參見以下 issue :

https://github.com/apache/dubbo/issues/2013

改進後的消費端線程池模型,通過複用業務端被阻塞的線程,很好的解決了這個問題。

老的線程池模型

我們重點關注 Consumer 部分:

業務線程發出請求,拿到一個 Future 實例。

業務線程緊接著調用 future.get 阻塞等待業務結果返回。

當業務數據返回後,交由獨立的 Consumer 端線程池進行反序列化等處理,並調用 future.set 將反序列化後的業務結果置回。

業務線程拿到結果直接返回

2.7.5 版本引入的線程池模型

業務線程發出請求,拿到一個 Future 實例。

在調用 future.get 之前,先調用 ThreadlessExecutor.wait,wait 會使業務線程在一個阻塞隊列上等待,直到隊列中被加入元素。

當業務數據返回後,生成一個 Runnable Task 並放入 ThreadlessExecutor 隊列

業務線程將 Task 取出並在本線程中執行:反序列化業務數據並 set 到 Future。

業務線程拿到結果直接返回

這樣,相比於老的線程池模型,由業務線程自己負責監測並解析返回結果,免去了額外的消費端線程池開銷。

關於性能優化,在接下來的版本中將會持續推進,主要從以下兩個方面入手:

RPC 調用鏈路。目前能看到的點包括:進一步減少執行鏈路的內存分配、在保證協議兼容性的前提下提高協議傳輸效率、提高 Filter、Router 等計算效率。

服務治理鏈路。進一步減少地址推送、服務治理規則推送等造成的內存、cpu 資源消耗。

5. TLS 安全傳輸鏈路

2.7.5 版本在傳輸鏈路的安全性上做了很多工作,對於內置的 Dubbo Netty Server 和新引入的 gRPC 協議都提供了基於 TLS 的安全鏈路傳輸機制。

TLS 的配置都有統一的入口,如下所示:

Provider 端

<code>SslConfig sslConfig = new SslConfig;/<code><code>sslConfig.setServerKeyCertChainPath("path to cert");/<code><code>sslConfig.setServerPrivateKeyPath(args[1]);/<code><code>// 如果開啟雙向 cert 認證/<code><code>if (mutualTls) {/<code><code> sslConfig.setServerTrustCertCollectionPath(args[2]);/<code><code>}/<code>
<code>ProtocolConfig protocolConfig = new ProtocolConfig("dubbo/grpc");/<code><code>protocolConfig.setSslEnabled(true);/<code>

Consumer 端

<code>if (!mutualTls) {}/<code><code> sslConfig.setClientTrustCertCollectionPath(args[0]);/<code><code>} else {/<code><code> sslConfig.setClientTrustCertCollectionPath(args[0]);/<code><code> sslConfig.setClientKeyCertChainPath(args[1]);/<code><code> sslConfig.setClientPrivateKeyPath(args[2]);/<code><code>}/<code>

為儘可能保證應用啟動的靈活性,TLS Cert 的指定還能通過 -D 參數或環境變量等方式來在啟動階段根據部署環境動態指定,具體請參見 Dubbo 配置讀取規則與 TLS 示例

Dubbo 配置讀取規則:http://dubbo.apache.org/zh-cn/docs/user/configuration/configuration-load-process.html

TLS 示例:https://github.com/apache/dubbo-samples/tree/master/java/dubbo-samples-ssl

如果要使用的是 gRPC 協議,在開啟 TLS 時會使用到協議協商機制,因此必須使用支持 ALPN 機制的 Provider,推薦使用的是 netty-tcnative,具體可參見 gRPC Java 社區的總結: https://github.com/grpc/grpc-java/blob/master/SECURITY.md

在服務調用的安全性上,Dubbo 在後續的版本中會持續投入,其中服務發現/調用的鑑權機制預計在接下來的版本中就會和大家見面。

6. Bootstrap API【beta】

在上面講《服務自省》時,我們提到了 Dubbo 面向接口的設計,面向接口編程、面向接口做服務發現和服務治理。在引入應用粒度服務發現的同時,2.7.5 版本對編程入口也做了優化,在兼容老版本 API 的同時,新增了新的面向應用的編程接口 - DubboBootstrap。

以面向 Dubbo API 編程為例,以前我們要這麼寫:

<code>ServiceConfig<greetingsservice> service1 = new ServiceConfig<>;/<greetingsservice>/<code><code>service1.setApplication(new ApplicationConfig("first-dubbo-provider"));/<code><code>service1.setRegistry(new RegistryConfig("zookeeper://" + zookeeperHost + ":2181"));/<code><code>service1.export;/<code>
<code>ServiceConfig<greetingsservice> service2 = new ServiceConfig<>;/<greetingsservice>/<code><code>service2.setApplication(new ApplicationConfig("first-dubbo-provider"));/<code><code>service2.setRegistry(new RegistryConfig("zookeeper://" + zookeeperHost + ":2181"));/<code><code>service2.export;/<code>
<code>....../<code>

ApplicationConfig、RegistryConfig、ProtocolConfig 等全局性的配置要在每個服務上去配置;並且從 Dubbo 框架的角度,由於缺少一個統一的 Server 入口,一些實例級別的配置如 ShutdownHook、ApplicationListener、應用級服務治理組件等都缺少一個加載驅動點。

在引入 DubboBootstrap 後,新的編程模型變得更簡單,並且也為解決了缺少實例級啟動入口的問題

<code>ProtocolConfig protocolConfig = new ProtocolConfig("grpc");/<code><code>protocolConfig.setSslEnabled(true);/<code>
<code>SslConfig sslConfig = new SslConfig;/<code><code>sslConfig.setXxxCert(...);/<code>
<code>DubboBootstrap bootstrap = DubboBootstrap.getInstance;/<code><code>bootstrap.application(new ApplicationConfig("ssl-provider"))/<code><code> .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))/<code><code> .protocol(protocolConfig)/<code><code> .ssl(sslConfig);/<code>
<code>ServiceConfig<greetingsservice> service1 = new ServiceConfig<>;/<greetingsservice>/<code><code>ServiceConfig<greetingsservice> service2 = new ServiceConfig<>;/<greetingsservice>/<code>
<code>bootstrap.service(service1).service(service2);/<code><code>bootstrap.start;/<code>

7. 多註冊中心集群負載均衡

對於多註冊中心訂閱的場景,選址時的多了一層註冊中心集群間的負載均衡:

在 Cluster Invoker 這一級,我們支持的選址策略有(2.7.5+ 版本,具體使用請參見文檔):

指定優先級

<code>/<code><code><registry>/<code>

同 zone 優先

<code>/<code><code><registry>/<code>

權重輪詢

<code>/<code><code><registry>/<code><code><registry>/<code>

默認,stick to 任意可用

關於多註冊中心訂閱模型,Dubbo 同時也提供了 Multi-Registry 合併的解決思路,歡迎參與到以下 PR 的討論中: https://github.com/apache/dubbo/issues/5399

8. 其他功能增強

新增地址變更事件通知接口,方便業務側感知地址變化

新增外圍配置加載入口,方便開發者在啟動階段定製服務啟動參數

config 模塊重構

parameters 擴展配置增強

其他一些 Bugfix

從 Dubbo 框架自身的角度來說,2.7.5 版本也做了很多的重構與優化(比如說 config 模塊的重構),這些改動對於使用者來說並無感知的,但是從優化整個 Dubbo 代碼內部結構的角度來說,這些改動對後續的功能開發與新機制的引入是一個很好的鋪墊。

9. 總結與展望

在後續的版本中,Dubbo 會持續快速的優化與迭代,主要從以下幾個方面發力:

繼續探索服務自省成為 Dubbo 主推的服務治理模型。

對於企業用戶關心的微服務解決方案場景,會持續推進框架的演進,包括當前正在開發的配置、服務鑑權機制、熔斷等功能。後續還會嘗試聯合社區推動周邊配套設施如網關、治理平臺 Admin 等的建設,非常期待社區能踴躍參與到此部分的建設中。

性能優化上。主要從兩個方面著手,一是調用鏈路的持續優化,同時繼續探索新的更通用的 RPC 協議;另一方面是在服務治理推送機制上的優化,以進一步提高 Dubbo 在大規模服務地址推送場景下的表現。

雲原生方向。接下來的版本將重點探索,1. 如何更好的支持 Dubbo 在 Kubernetes 上的部署和服務治理;2. 對於混合部署的場景,如傳統 VM 和 K8S 體系混合部署、SDK Dubbo 與 Mesh 混合部署的場景,如何提供更好的支持以實現混部場景的長期共存或遷移。

高可用架構

改變互聯網的構建方式