Dubbo2.7 三大新特性詳解
自 2017 年 7 月阿里重啟 Dubbo 開源,到目前為止 github star 數,contributor 數都有了非常大的提升。2018 年 2 月 9 日阿里決定將 Dubbo 專案貢獻給 Apache,經過一週的投票,順利成為了 Apache 的孵化專案,也就是大家現在看到的 Incubator Dubbo 。預計在 2019 年 4 月,Dubbo 可以達成畢業,成為 Apache 的頂級專案。
分支介紹
Dubbo 目前有如圖所示的 5 個分支,其中 2.7.1-release 只是一個臨時分支,忽略不計,對其他 4 個分支進行介紹。
- 2.5.x 近期已經通過投票,Dubbo 社群即將停止對其的維護。
- 2.6.x 為長期支援的版本,也是 Dubbo 貢獻給 Apache 之前的版本,其包名字首為:com.alibaba,JDK 版本對應 1.6。
- 3.x-dev 是前瞻性的版本,對 Dubbo 進行一些高階特性的補充,如支援 rx 特性。
- master 為長期支援的版本,版本號為 2.7.x,也是 Dubbo 貢獻給 Apache 的開發版本,其包名字首為:org.apache,JDK 版本對應 1.8。
如果想要研究 Dubbo 的原始碼,建議直接瀏覽 master 分支。
Dubbo 2.7 新特性
Dubbo 2.7.x 作為 Apache 的孵化版本,除了程式碼優化之外,還新增了許多重磅的新特性,本文將會介紹其中最典型的三個新特性:
- 非同步化改造
- 三大中心改造
- 服務治理增強
非同步化改造
幾種呼叫方式
在遠端方法呼叫中,大致可以分為這 4 種呼叫方式。oneway 指的是客戶端傳送訊息後,不需要接受響應。對於那些不關心服務端響應的請求,比較適合使用 oneway 通訊。
注意,void hello() 方法在遠端方法呼叫中,不屬於 oneway 呼叫,雖然 void 方法表達了不關心返回值的語義,但在 RPC 層面,仍然需要做通訊層的響應。
sync 是最常用的通訊方式,也是預設的通訊方法。
future 和 callback 都屬於非同步呼叫的範疇,他們的區別是:在接收響應時,future.get() 會導致執行緒的阻塞;callback 通常會設定一個回撥執行緒,當接收到響應時,自動執行,不會對當前執行緒造成阻塞。
Dubbo 2.6 非同步化
非同步化的優勢在於客戶端不需要啟動多執行緒即可完成並行呼叫多個遠端服務,相對多執行緒開銷較小。介紹 2.7 中的非同步化改造之前,先回顧一下如何在 2.6 中使用 Dubbo 非同步化的能力。
- 將同步介面宣告成
async=true
<dubbo:reference id="asyncService" interface="org.apache.dubbo.demo.api.AsyncService" async="true"/>
public interface AsyncService { String sayHello(String name); }
- 通過上下文類獲取 future
AsyncService.sayHello("Han Meimei"); Future<String> fooFuture = RpcContext.getContext().getFuture(); fooFuture.get();
可以看出,這樣的使用方式,不太符合非同步程式設計的習慣,竟然需要從一個上下文類中獲取到 Future。如果同時進行多個非同步呼叫,使用不當很容易造成上下文汙染。而且,Future 並不支援 callback 的呼叫方式。這些弊端在 Dubbo 2.7 中得到了改進。
Dubbo 2.7 非同步化
- 無需配置中特殊宣告,顯示宣告非同步介面即可
public interface AsyncService { String sayHello(String name); default CompletableFuture<String> sayHiAsync(String name) { return CompletableFuture.completedFuture(sayHello(name)); } }
- 使用 callback 方式處理返回值
CompletableFuture<String> future = asyncService.sayHiAsync("Han MeiMei"); future.whenComplete((retValue, exception) -> { if (exception == null) { System.out.println(retValue); } else { exception.printStackTrace(); } });
Dubbo 2.7 中使用了 JDK1.8 提供的 CompletableFuture
原生介面對自身的非同步化做了改進。 CompletableFuture
可以支援 future 和 callback 兩種呼叫方式,使用者可以根據自己的喜好和場景選擇使用,非常靈活。
非同步化設計 FAQ
Q:如果 RPC 介面只定義了同步介面,有辦法使用非同步呼叫嗎?
A:2.6 中的非同步呼叫唯一的優勢在於,不需要在介面層面做改造,又可以進行非同步呼叫,這種方式仍然在 2.7 中保留;使用 Dubbo 官方提供的 compiler hacker,編譯期自動重寫同步方法,請 在此 討論和跟進具體進展。
Q:關於非同步介面的設計問題,為何不提供編譯外掛,根據原介面,自動編譯出一個 XxxAsync 介面?
A:Dubbo 2.7 採用採用過這種設計,但介面的膨脹會導致服務類的增量釋出,而且介面名的變化會影響服務治理的一些相關邏輯,改為方法新增 Async 字尾相對影響範圍較小。
Q:Dubbo 分為了客戶端非同步和服務端非同步,剛剛你介紹的是客戶端非同步,為什麼不提服務端非同步呢?
A:Dubbo 2.7 新增了服務端非同步的支援,但實際上,Dubbo 的業務執行緒池模型,本身就可以理解為非同步呼叫,個人認為服務端非同步的特性較為雞肋。
三大中心改造
三大中心指的:註冊中心,元資料中心,配置中心。
在 2.7 之前的版本,Dubbo 只配備了註冊中心,主流使用的註冊中心為 zookeeper。新增加了元資料中心和配置中心,自然是為了解決對應的痛點,下面我們來詳細闡釋三大中心改造的原因。
元資料改造
元資料是什麼?元資料定義為描述資料的資料,在服務治理中,例如服務介面名,重試次數,版本號等等都可以理解為元資料。在 2.7 之前,元資料一股腦丟在了註冊中心之中,這造成了一系列的問題:
推送量大 -> 儲存資料量大 -> 網路傳輸量大 -> 延遲嚴重
生產者端註冊 30+ 引數,有接近一半是不需要作為註冊中心進行傳遞;消費者端註冊 25+ 引數,只有個別需要傳遞給註冊中心。有了以上的理論分析,Dubbo 2.7 進行了大刀闊斧的改動,只將真正屬於服務治理的資料釋出到註冊中心之中,大大降低了註冊中心的負荷。
同時,將全量的元資料釋出到另外的元件中:元資料中心。元資料中心目前支援 redis(推薦),zookeeper。這也為 Dubbo 2.7 全新的 Dubbo Admin 做了準備,關於新版的 Dubbo Admin,我將會後續準備一篇獨立的文章進行介紹。
示例:使用 zookeeper 作為元資料中心
<dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>
Dubbo 2.6 元資料
dubbo://30.5.120.185:20880/com.alibaba.dubbo.demo.DemoService? anyhost=true& application=demo-provider& interface=com.alibaba.dubbo.demo.DemoService& methods=sayHello& bean.name=com.alibaba.dubbo.demo.DemoService& dubbo=2.0.2& executes=4500& generic=false& owner=kirito& pid=84228& retries=7& side=provider& timestamp=1552965771067
從本地的 zookeeper 中取出一條服務資料,通過解碼之後,可以看出,的確有很多引數是不必要。
Dubbo 2.7 元資料
在 2.7 中,如果不進行額外的配置,zookeeper 中的資料格式仍然會和 Dubbo 2.6 保持一致,這主要是為了保證相容性,讓 Dubbo 2.6 的客戶端可以呼叫 Dubbo 2.7 的服務端。如果整體遷移到 2.7,則可以為註冊中心開啟簡化配置的引數:
<dubbo:registry address=“zookeeper://127.0.0.1:2181” simplified="true"/>
Dubbo 將會只上傳那些必要的服務治理資料,一個簡化過後的資料如下所示:
dubbo://30.5.120.185:20880/org.apache.dubbo.demo.api.DemoService? application=demo-provider& dubbo=2.0.2& release=2.7.0& timestamp=1552975501873
對於那些非必要的服務資訊,仍然全量儲存在元資料中心之中:
元資料中心的資料可以被用於服務測試,服務 MOCK 等功能。目前註冊中心配置中 simplified 的預設值為 false,因為考慮到了遷移的相容問題,在後續迭代中,預設值將會改為 true。
配置中心支援
衡量配置中心的必要性往往從三個角度出發:
-
分散式配置統一管理
-
動態變更推送
-
安全性
Spring Cloud Config, Apollo, Nacos 等分散式配置中心元件都對上述功能有不同程度的支援。在 2.7 之前的版本中,在 zookeeper 中設定了部分節點:configurators,routers,用於管理部分配置和路由資訊,它們可以理解為 Dubbo 配置中心的雛形。在 2.7 中,Dubbo 正式支援了配置中心,目前支援的幾種註冊中心 Zookeeper,Apollo,Nacos(2.7.1-release 支援)。
在 Dubbo 中,配置中心主要承擔了兩個作用
-
外部化配置。啟動配置的集中式儲存
-
服務治理。服務治理規則的儲存與通知
示例:使用 Zookeeper 作為配置中心
<dubbo:config-center address="zookeeper://127.0.0.1:2181"/>
引入配置中心後,需要注意配置項的覆蓋問題,優先順序如圖所示
服務治理增強
我更傾向於將 Dubbo 當做一個服務治理框架,而不僅僅是一個 RPC 框架。在 2.7 中,Dubbo 對其服務治理能力進行了增強,增加了標籤路由的能力,並抽象出了應用路由和服務路由的概念。在最後一個特性介紹中,著重對標籤路由 TagRouter 進行探討。
在服務治理中,路由層和負載均衡層的對比。區別 1,Router:m 選 n,LoadBalance:n 選 1;區別 2,路由往往是疊加使用的,負載均衡只能配置一種。
在很長的一段時間內,Dubbo 社群經常有人提的一個問題是:Dubbo 如何實現流量隔離和灰度釋出,直到 2.7 提供了標籤路由,使用者可以使用這個功能,來實現上述的需求。
標籤路由提供了這樣一個能力,當呼叫鏈路為 A -> B -> C -> D 時,使用者給請求打標,最典型的打標方式可以藉助 attachment(他可以在分散式呼叫中傳遞下去),呼叫會優先請求那些匹配的服務端,如 A -> B,C -> D,由於叢集中未部署 C 節點,則會降級到普通節點。
打標方式會收到整合系統差異的影響,從而導致很大的差異,所以 Dubbo 只提供了 RpcContext.getContext().setAttachment()
這樣的基礎介面,使用者可以使用 SPI 擴充套件,或者 server filter 的擴充套件,對測試流量進行打標,引導進入隔離環境/灰度環境。
新版的 Dubbo Admin 提供了標籤路由的配置項:
Dubbo 使用者可以在自己系統的基礎上對標籤路由進行二次擴充套件,或者借鑑標籤路由的設計,實現自己系統的流量隔離,灰度釋出。
總結
本文介紹了 Dubbo 2.7 比較重要的三大新特性:非同步化改造,三大中心改造,服務治理增強。Dubbo 2.7 還包含了很多功能優化、特性升級,可以在專案原始碼的 CHANGES.md 中瀏覽全部的改動點。最後提供一份 Dubbo 2.7 的升級文件: 2.7遷移文件 ,歡迎體驗。
歡迎關注我的微信公眾號:「Kirito的技術分享」,關於文章的任何疑問都會得到回覆,帶來更多 Java 相關的技術分享。