Dubbo下一站:Apache頂級專案
導讀:
近日,在Apache Dubbo開發者沙龍杭州站的活動中,阿里巴巴中介軟體技術專家曹勝利(展圖)向開發者們分享了Dubbo2.7版本的規劃。
本文將為你探祕 Dubbo 2.7背後的思考和實現方式。
作者:(按姓氏拼音排序,排名不分先後)
曹勝利(展圖):Apache Dubbo Committer。
劉軍(陸龜):Apache Dubbo Committer。
Dubbo 2.7 將圍繞 非同步支援優化、元資料改造,引入JDK8的特性、Netty4.0的特性以及MetricsAPI 5個方面提升服務呼叫和服務治理的效率,以及可擴充套件性,同時將修復社群提出的若干問題。
據悉,2.7.x會作為Dubbo在Apache社群的畢業版本,Dubbo將有機會成為繼RocketMQ後,來自阿里巴巴的又一個Apache頂級專案(TLP)。
優化對非同步的支援
基於Dubbo實現全非同步程式設計,是在2.7.0版本中對現有非同步方式增強後新引入的功能。之前的版本對非同步支援用起來不是很友好,存在若干問題,2.7版本將基於JDK8 中的CompletableFuture做出一些針對性的增強,同時新增了@Dubboasync的註解,通過這個註解可以生成非同步化相關的程式碼。
» 2.6.x版本之前的非同步方式
在2.6.x及之前的版本提供了一定的非同步程式設計能力,包括Consumer端非同步呼叫、引數回撥、事件通知等。但當前的非同步方式存在以下問題:
Future獲取方式不夠直接;
Future介面無法實現自動回撥,而自定義ResponseFuture雖支援回撥但支援的非同步場景有限,如不支援Future間的相互協調或組合等;
不支援Provider端非同步
以Consumer端非同步使用方式為例:
1、定義一個普通的同步介面並宣告支援非同步呼叫
public interface FooService { String findFoo(String name);} <dubbo:reference id="fooService" interface="com.alibaba.foo.FooService"> <dubbo:method name="findFoo" async="true" /> </dubbo:reference>
2、通過RpcContext獲取Future
// 此呼叫會立即返回nullfooService.findFoo(fooId);// 拿到呼叫的Future引用,當結果返回後,會被通知和設定到此FutureFuture<Foo> fooFuture = RpcContext.getContext().getFuture();fooFuture.get();
或
// 此呼叫會立即返回nullfooService.findFoo(fooId);// 拿到Dubbo內建的ResponseFuture並設定回撥ResponseFuture future = ((FutureAdapter)RpcContext.getContext().getFuture()).getFuture();future.setCallback(new ResponseCallback() {
@Override
public void done(Object response) {
System.out.print(response);
}
@Override
public void caught(Throwable exception) {
exception.printStackTrace();
}});
從這個簡單的示例我們可以體會到一些使用中的不便之處:
- findFoo的同步介面,不能直接返回代表非同步結果的Future,通過RpcContext進一步獲取。
- Future只支援阻塞式的get()介面獲取結果。
- 通過獲取內建的ResponseFuture介面,可以設定回撥。但獲取ResponseFuture的API使用不便,且僅支援設定回撥其他非同步場景均不支援,如多個Future協同工作的場景等。
» 2.7.0基於CompletableFuture的增強
瞭解Java中Future演進歷史的同學應該知道,Dubbo 2.6.x及之前版本中使用的Future是在Java 5中引入的,所以存在以上一些功能設計上的問題,而在Java 8中引入的CompletableFuture進一步豐富了Future介面,很好的解決了這些問題。
Dubbo在2.7.0版本已經升級了對Java 8的支援,同時基於CompletableFuture對當前的非同步功能進行了增強。
1、支援直接定義返回CompletableFuture的服務介面。通過這種型別的介面,我們可以更自然的實現Consumer、Provider端的非同步程式設計。
public interface AsyncService {
CompletableFuture<String> sayHello(String name);}
2、如果你不想將介面的返回值定義為Future型別,或者存在定義好的同步型別介面,則可以額外定義一個非同步介面並提供Future型別的方法。
public interface GreetingsService {
String sayHi(String name);}
@AsyncFor(GreetingsService.class)public interface GrettingServiceAsync extends GreetingsService {
CompletableFuture<String> sayHiAsync(String name);}
這樣,Provider可以只實現sayHi方法;而Consumer通過直接呼叫sayHiAsync可以拿到一個Future例項,Dubbo框架在Provider端會自動轉換為對sayHi方法的呼叫。為每個同步方法提供一個非同步方法定義會比較麻煩,更進一步的,利用Dubbo生態中的AnnotationProcessor實現,可以自動幫我們自動生成非同步方法定義。
3、同樣的,如果你的原始介面定義不是Future型別的返回值,Provider端非同步也提供了類似Servlet3.0裡的Async Servlet的程式設計介面: RpcContext.startAsync()。
public interface AsyncService {
String sayHello(String name);}
public class AsyncServiceImpl implements AsyncService {
public String sayHello(String name) {
final AsyncContext asyncContext = RpcContext.startAsync();
new Thread(() -> {
asyncContext.write("Hello " + name + ", response from provider.");
}).start();
return null;
}}
在方法體的開始RpcContext.startAsync()啟動非同步,並開啟新執行緒非同步的執行業務邏輯,在耗時操作完成後通過asyncContext.write將結果寫回。
4、RpcContext直接返回CompletableFuture
CompletableFuture<String> f = RpcContext.getContext().getCompletableFuture();
以上所有的增強,是在相容已有非同步程式設計的基礎上進行的,因此基於2.6.x版本編寫的非同步程式不用做任何改造即可順利執行。
元資料改造
元資料的改造主要是從適配微服務註冊中心、配置中心分離的模型、減輕註冊中心壓力、提高服務治理能力和效率的角度來執行的。目前版本的Dubbo在註冊中心的URL有數十個key/value的鍵值對,包含了一個服務的所有元資料。在大規模實踐的基礎上,我們逐漸發現這樣組織的元資料存在一些問題:
- 註冊中心儲存的URL過長:
導致儲存壓力驟增,變更事件的推送效率明顯下降;同時給訂閱方帶來了額外的計算壓力,尤其是大規模場景下的記憶體,增長顯著。
- 註冊中心承擔了過多服務治理配置的功能:
負責初始配置的同步,同時負責儲存各種執行期配置規則。這一方面加劇了註冊中心的壓力,另一方面配置規則的靈活性也受到了一定的限制,同時也無法利用一些更專業的微服務配置中心帶來的強大功能。
- 屬性的功能定位不清晰:
methods, pid, owner看起來都是為服務查詢服務而註冊的屬性,但當我們實際開發或操作服務管控系統時,卻發現這樣簡陋的資訊是很難滿足查詢治理需求的。我們更多的屬性,需要更豐富的註冊資料。以methods為例,雖然方法列表的內容已經很長了,但當我們要在OPS開發服務測試/mock功能時,卻發現需要的方法簽名等資料還是無法獲取。
概括以上問題,我們將URL中的元資料劃分了三個部分:
- 元資料資訊
介面的完整定義:包含介面名,介面所含的方法,以及方法所含的出入參資訊。對於服務測試和服務mock有非常重要的作用。
- 執行鏈路上資料
需要將引數從provider端傳遞給消費者端,讓消費者端感知到的。如token,timeout等。
- 服務自持有配置&Ops需求
只有在provider端或者消費者端需要使用的,如executes, document等。
支援配置中心
配置中心是dubbo.properties的動態版本,支援的粒度包括全域性的、應用級別的和服務級別的等維度。通過上面的元資料改造,配置中心支援,再加上原有的註冊中心,Dubbo體系裡就會存在:
- 註冊中心:
理想情況下,註冊中心將只用於關鍵服務資訊(核心鏈路)的同步,進一步減輕註冊中心的儲存壓力,提高地址同步效率,同時緩解當前由於URL冗餘在大規模推送時造成的Consumer端記憶體計算壓力。
- 配置中心:
解決當前配置和地址資訊耦合的問題,通過抽象動態配置層,讓開發者可以對接微服務場景下更常用的、更專業的配置中心,如Nacos, Apollo, Consul, Etcd等;提供更靈活的、更豐富的配置規則,包括服務、應用不同粒度的配置,更豐富的路由規則,集中式管理的動態引數規則等。
- 服務查詢治理中心(含元資料)
對於純粹的服務查詢相關的資料,包括Consumer的服務訂閱資料,往往都是註冊後不可變的並且不需要節點間的同步,如當前URL可以看到的methods、owner等key以及所有的Consumer端URL。
因此我們在2.7.0中引入了儲存模組,專門用來存放這部分資料,這部分將會和新版本的Dubbo-ops密切整合,作為豐富的服務查詢、測試等功能的資料基礎,因此這部分的資料將會得到進一步的豐富。總體來說否開啟此功能對使用者將是可選的,並且實現上也將是可擴充套件的,如我們計劃支援Redis, Zookeeper等。
- 路由規則
Dubbo 提供了具有一定擴充套件性的路由規則,其中具有代表性的是條件路由和指令碼路由。2.6.x及以下版本存在的問題:
- 路由規則儲存在註冊中心
- 只支援服務粒度的路由,應用級別無法定義路由規則
- 支援路由快取,但基本不具有擴充套件性
- 一個服務或應用允許定義多條路由規則,服務治理無法管控
- 實現上,每條規則生成一個Router例項並動態載入
從問題出發我們重新設計,將原來的路由配置從註冊中心遷往配置中心。明確了配置和服務發現的邊界。新增了RouterChain,用於重構路由規則邏輯,新增應用級別路由,Tag路由優化等。針對服務級別的路由,精確到單個服務,避免了無法明確路由規則的問題。
我們簡單概括下各個類的協作關係。
- RegistryDirectory,包含完整的地址列表,直接對接註冊中心,並動態接收註冊中心地址變更。
- RouterChain,由Router組裝成的列表,是路由動作的入口,接收傳入的地址列表並將過濾後的地址列表返回給呼叫方,而具體的過濾動作則委託給Router執行
- Router,接收並解析路由規則,接收地址列表,根據路由規則完成過濾動作,並返回過濾後的地址列表。其本身也是一個ConfigurationListener,隨時接收路由規則更新。
- ConfigurationListener,動態配置變更的回撥介面
- DynamicConfiguration,動態配置SPI,支援的擴充套件實現包括Zookeeper、Apollo、Nacos等
Dubbo 將在近期正式釋出2.7.0版本,恰值Dubbo宣佈重啟一週年。這一年,Dubbo 共釋出了13個版本,社群共有24位PPMC/Committer,144位Contributor,在北京、上海、深圳、成都和杭州舉辦了5場開發者沙龍,但技術開源的道路並沒有止境,我們歡迎更多的開發者們可以參與進來,併到Dubbo meetup來進行分享,一起建設Dubbo生態。
閱讀原文
更多技術乾貨 請關注阿里云云棲社群微訊號 :yunqiinsight