導讀:
近日,在Apache Dubbo開發(fā)者沙龍杭州站的活動中,阿里巴巴中間件技術專家曹勝利(展圖)向開發(fā)者們分享了Dubbo2.7版本的規(guī)劃。
本文將為你探秘 Dubbo 2.7背后的思考和實現(xiàn)方式。
作者:(按姓氏拼音排序,排名不分先后)
曹勝利(展圖):Apache Dubbo Committer。
劉軍(陸龜):Apache Dubbo Committer。
Dubbo 2.7 將圍繞 異步支持優(yōu)化、元數(shù)據(jù)改造,引入JDK8的特性、Netty4.0的特性以及MetricsAPI 5個方面提升服務調(diào)用和服務治理的效率,以及可擴展性,同時將修復社區(qū)提出的若干問題。
據(jù)悉,2.7.x會作為Dubbo在Apache社區(qū)的畢業(yè)版本,Dubbo將有機會成為繼RocketMQ后,來自阿里巴巴的又一個Apache頂級項目(TLP)。
優(yōu)化對異步的支持
基于Dubbo實現(xiàn)全異步編程,是在2.7.0版本中對現(xiàn)有異步方式增強后新引入的功能。之前的版本對異步支持用起來不是很友好,存在若干問題,2.7版本將基于JDK8 中的CompletableFuture做出一些針對性的增強,同時新增了@Dubboasync的注解,通過這個注解可以生成異步化相關的代碼。
? 2.6.x版本之前的異步方式
在2.6.x及之前的版本提供了一定的異步編程能力,包括Consumer端異步調(diào)用、參數(shù)回調(diào)、事件通知等。但當前的異步方式存在以下問題:
Future獲取方式不夠直接;
Future接口無法實現(xiàn)自動回調(diào),而自定義ResponseFuture雖支持回調(diào)但支持的異步場景有限,如不支持Future間的相互協(xié)調(diào)或組合等;
不支持Provider端異步
以Consumer端異步使用方式為例:
1、定義一個普通的同步接口并聲明支持異步調(diào)用
public?interface?FooService?{String?findFoo(String?name);}
?? ? 2、通過RpcContext獲取Future
//?此調(diào)用會立即返回nullfooService.findFoo(fooId);//?拿到調(diào)用的Future引用,當結(jié)果返回后,會被通知和設置到此FutureFuture?fooFuture?=?RpcContext.getContext().getFuture();fooFuture.get();
或
//?此調(diào)用會立即返回nullfooService.findFoo(fooId);//?拿到Dubbo內(nèi)置的ResponseFuture并設置回調(diào)ResponseFuture?future?=?((FutureAdapter)RpcContext.getContext().getFuture()).getFuture();future.setCallback(new?ResponseCallback()?{@Overridepublic?void?done(Object?response)?{
????System.out.print(response);
}@Overridepublic?void?caught(Throwable?exception)?{
????exception.printStackTrace();
}});從這個簡單的示例我們可以體會到一些使用中的不便之處:
findFoo的同步接口,不能直接返回代表異步結(jié)果的Future,通過RpcContext進一步獲取。
Future只支持阻塞式的get()接口獲取結(jié)果。
通過獲取內(nèi)置的ResponseFuture接口,可以設置回調(diào)。但獲取ResponseFuture的API使用不便,且僅支持設置回調(diào)其他異步場景均不支持,如多個Future協(xié)同工作的場景等。
? 2.7.0基于CompletableFuture的增強
了解Java中Future演進歷史的同學應該知道,Dubbo 2.6.x及之前版本中使用的Future是在Java 5中引入的,所以存在以上一些功能設計上的問題,而在Java 8中引入的CompletableFuture進一步豐富了Future接口,很好的解決了這些問題。
Dubbo在2.7.0版本已經(jīng)升級了對Java 8的支持,同時基于CompletableFuture對當前的異步功能進行了增強。
1、支持直接定義返回CompletableFuture的服務接口。通過這種類型的接口,我們可以更自然的實現(xiàn)Consumer、Provider端的異步編程。
public?interface?AsyncService?{CompletableFuture?sayHello(String?name);} 2、如果你不想將接口的返回值定義為Future類型,或者存在定義好的同步類型接口,則可以額外定義一個異步接口并提供Future類型的方法。
public?interface?GreetingsService?{String?sayHi(String?name);}@AsyncFor(GreetingsService.class)public?interface?GrettingServiceAsync?extends?GreetingsService?{CompletableFuture?sayHiAsync(String?name);} 這樣,Provider可以只實現(xiàn)sayHi方法;而Consumer通過直接調(diào)用sayHiAsync可以拿到一個Future實例,Dubbo框架在Provider端會自動轉(zhuǎn)換為對sayHi方法的調(diào)用。為每個同步方法提供一個異步方法定義會比較麻煩,更進一步的,利用Dubbo生態(tài)中的AnnotationProcessor實現(xiàn),可以自動幫我們自動生成異步方法定義。
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()啟動異步,并開啟新線程異步的執(zhí)行業(yè)務邏輯,在耗時操作完成后通過asyncContext.write將結(jié)果寫回。
4、RpcContext直接返回CompletableFuture
CompletableFuture?f?=?RpcContext.getContext().getCompletableFuture();
以上所有的增強,是在兼容已有異步編程的基礎上進行的,因此基于2.6.x版本編寫的異步程序不用做任何改造即可順利運行。
元數(shù)據(jù)改造
元數(shù)據(jù)的改造主要是從適配微服務注冊中心、配置中心分離的模型、減輕注冊中心壓力、提高服務治理能力和效率的角度來執(zhí)行的。目前版本的Dubbo在注冊中心的URL有數(shù)十個key/value的鍵值對,包含了一個服務的所有元數(shù)據(jù)。在大規(guī)模實踐的基礎上,我們逐漸發(fā)現(xiàn)這樣組織的元數(shù)據(jù)存在一些問題:
注冊中心存儲的URL過長:
導致存儲壓力驟增,變更事件的推送效率明顯下降;同時給訂閱方帶來了額外的計算壓力,尤其是大規(guī)模場景下的內(nèi)存,增長顯著。
注冊中心承擔了過多服務治理配置的功能:
負責初始配置的同步,同時負責存儲各種運行期配置規(guī)則。這一方面加劇了注冊中心的壓力,另一方面配置規(guī)則的靈活性也受到了一定的限制,同時也無法利用一些更專業(yè)的微服務配置中心帶來的強大功能。
屬性的功能定位不清晰:
methods, pid, owner看起來都是為服務查詢服務而注冊的屬性,但當我們實際開發(fā)或操作服務管控系統(tǒng)時,卻發(fā)現(xiàn)這樣簡陋的信息是很難滿足查詢治理需求的。我們更多的屬性,需要更豐富的注冊數(shù)據(jù)。以methods為例,雖然方法列表的內(nèi)容已經(jīng)很長了,但當我們要在OPS開發(fā)服務測試/mock功能時,卻發(fā)現(xiàn)需要的方法簽名等數(shù)據(jù)還是無法獲取。
概括以上問題,我們將URL中的元數(shù)據(jù)劃分了三個部分:
元數(shù)據(jù)信息
接口的完整定義:包含接口名,接口所含的方法,以及方法所含的出入?yún)⑿畔?。對于服務測試和服務mock有非常重要的作用。
執(zhí)行鏈路上數(shù)據(jù)
需要將參數(shù)從provider端傳遞給消費者端,讓消費者端感知到的。如token,timeout等。
服務自持有配置&Ops需求
只有在provider端或者消費者端需要使用的,如executes, document等。
支持配置中心
配置中心是dubbo.properties的動態(tài)版本,支持的粒度包括全局的、應用級別的和服務級別的等維度。通過上面的元數(shù)據(jù)改造,配置中心支持,再加上原有的注冊中心,Dubbo體系里就會存在:
注冊中心:
理想情況下,注冊中心將只用于關鍵服務信息(核心鏈路)的同步,進一步減輕注冊中心的存儲壓力,提高地址同步效率,同時緩解當前由于URL冗余在大規(guī)模推送時造成的Consumer端內(nèi)存計算壓力。
配置中心:
解決當前配置和地址信息耦合的問題,通過抽象動態(tài)配置層,讓開發(fā)者可以對接微服務場景下更常用的、更專業(yè)的配置中心,如Nacos, Apollo, Consul, Etcd等;提供更靈活的、更豐富的配置規(guī)則,包括服務、應用不同粒度的配置,更豐富的路由規(guī)則,集中式管理的動態(tài)參數(shù)規(guī)則等。
服務查詢治理中心(含元數(shù)據(jù))
對于純粹的服務查詢相關的數(shù)據(jù),包括Consumer的服務訂閱數(shù)據(jù),往往都是注冊后不可變的并且不需要節(jié)點間的同步,如當前URL可以看到的methods、owner等key以及所有的Consumer端URL。
因此我們在2.7.0中引入了存儲模塊,專門用來存放這部分數(shù)據(jù),這部分將會和新版本的Dubbo-ops密切整合,作為豐富的服務查詢、測試等功能的數(shù)據(jù)基礎,因此這部分的數(shù)據(jù)將會得到進一步的豐富??傮w來說否開啟此功能對用戶將是可選的,并且實現(xiàn)上也將是可擴展的,如我們計劃支持Redis, Zookeeper等。
路由規(guī)則
Dubbo 提供了具有一定擴展性的路由規(guī)則,其中具有代表性的是條件路由和腳本路由。2.6.x及以下版本存在的問題:
路由規(guī)則存儲在注冊中心
只支持服務粒度的路由,應用級別無法定義路由規(guī)則
支持路由緩存,但基本不具有擴展性
一個服務或應用允許定義多條路由規(guī)則,服務治理無法管控
實現(xiàn)上,每條規(guī)則生成一個Router實例并動態(tài)加載
從問題出發(fā)我們重新設計,將原來的路由配置從注冊中心遷往配置中心。明確了配置和服務發(fā)現(xiàn)的邊界。新增了RouterChain,用于重構路由規(guī)則邏輯,新增應用級別路由,Tag路由優(yōu)化等。針對服務級別的路由,精確到單個服務,避免了無法明確路由規(guī)則的問題。

我們簡單概括下各個類的協(xié)作關系。
RegistryDirectory,包含完整的地址列表,直接對接注冊中心,并動態(tài)接收注冊中心地址變更。
RouterChain,由Router組裝成的列表,是路由動作的入口,接收傳入的地址列表并將過濾后的地址列表返回給調(diào)用方,而具體的過濾動作則委托給Router執(zhí)行
Router,接收并解析路由規(guī)則,接收地址列表,根據(jù)路由規(guī)則完成過濾動作,并返回過濾后的地址列表。其本身也是一個ConfigurationListener,隨時接收路由規(guī)則更新。
ConfigurationListener,動態(tài)配置變更的回調(diào)接口
DynamicConfiguration,動態(tài)配置SPI,支持的擴展實現(xiàn)包括Zookeeper、Apollo、Nacos等
Dubbo 將在近期正式發(fā)布2.7.0版本,恰值Dubbo宣布重啟一周年。這一年,Dubbo 共發(fā)布了13個版本,社區(qū)共有24位PPMC/Committer,144位Contributor,在北京、上海、深圳、成都和杭州舉辦了5場開發(fā)者沙龍,但技術開源的道路并沒有止境,我們歡迎更多的開發(fā)者們可以參與進來,并到Dubbo meetup來進行分享,一起建設Dubbo生態(tài)。
電子發(fā)燒友App




























































評論