Dubbo集羣模塊的目的是將集羣Invokers構造一個透明的Invoker對象,其中包含了容錯機制、負載均衡、目錄服務(服務地址集合)、路由機制等,爲RPC層提供高可用、高併發、自動發現、可治理的SOA特性。html
本文咱們主要討論如下八個問題:數組
1、集羣模塊的需求功能點有哪些?緩存
2、集羣模塊的整體設計框架是什麼樣的?服務器
3、Dubbo提供了哪些容錯機制?如何實現的?併發
4、Dubbo提供了哪些負載均衡機制?如何實現的?app
5、Dubbo目錄服務是幹什麼的?提供了哪幾種類型的目錄服務?負載均衡
6、Dubbo提供了哪些路由機制?如何實現的?框架
7、總結集羣模塊如何帶來高可用、自動發現、可治理的特性?dom
1、集羣模塊的需求功能點有哪些?分佈式
基於文章開頭討論的目標內容,我認爲集羣模塊的需求功能點主要有如下幾點:
一、將集羣Invokers構造一個透明的Invoker對象提供給Rpc模塊調用;
二、提供容錯機制,包括Failover(失敗自動切換,嘗試其餘服務器)、Failfast(失敗當即返回並拋出異常)、Failsafe(失敗忽略異常)、Failback(失敗自動恢復,記錄日誌並定時重試)、Forking(並行調用多個服務,一個成功當即返回)、Broadcast(廣播調用全部提供者,任意一個報錯則報錯);
三、提供負載均衡機制,包括Random(帶權重的隨機訪問)、RoundRobin(帶權重的輪訓訪問)、LeastActive(選擇最少活躍者)、ConsistentHash(一致性哈希,相同參數的請求老是發到同一提供者);
四、提供目錄服務,包括靜態目錄服務(將調用地址存到本地)、註冊中心註冊目錄服務兩種方式;
五、提供路由機制,包括條件路由(在URL配置條件表達式)和腳本路由(使用腳本語言編寫腳本,返回路由結果);
2、集羣模塊的整體設計框架是什麼樣的?
調用關係以下圖所示:
接口聲明以下圖所示:
Cluster接口:聲明join()方法,從Directory實例中的Invoker列表中返回一個Invoker;
Directory接口:list()方法,可查詢Invoker列表;
LoadBalance接口:select()方法,可從一個Invoker集合中結合負載均衡策略選擇一個Invoker返回;
Router接口:route()方法,從給定的Invoker集合路由選擇一個Invoker返回;
Merger接口:merge(T)方法,將多個調用返回的結果合併起來返回;
Configurator接口:configure(URL)方法,配置加工URL參數並返回;
3、Dubbo提供了哪些集羣容錯機制?如何實現的?
因爲篇幅過程,我將其內容單獨寫了一篇博客,詳見 《集羣容錯機制》。
4、Dubbo提供了哪些負載均衡機制?如何實現的?
因爲篇幅過長,我又將內容拆分到另外一片博客,見《集羣負載均衡》。
5、Dubbo目錄服務是幹什麼的?提供了哪幾種類型的目錄服務?
dubbo目錄服務提供了獲取提供者服務地址列表的功能,目錄服務的調用者是com.alibaba.dubbo.rpc.cluster.Cluster的join()方法。目前dubbo提供了靜態目錄服務和註冊中心目錄服務。靜態目錄服務實現了一個靜態的地址列表本地內存緩存。註冊中心目錄服務提供了分佈式註冊檢索服務地址的功能。在提供目錄服務返回服務地址的時候,調用dubbo的路由服務,實現了請求服務的路由功能。接下來,咱們來討論目錄服務的實現細節。
一、Directory接口
Directory接口中,
Class<T> getInterface()方法返回服務的接口類;
list(Invocation invocation)就是獲取服務地址列表的方法;
父接口Node中,
getUrl()方法返回了服務聲明的URL信息,
isAvailable()方法判斷目錄服務是否可用,或者是否存在可用的服務提供者;
destroy()銷燬目錄服務及全部提供者服務。
二、AbstractDirectory,目錄服務的默認實現抽象類;
(1)將路由對象引入目錄服務,方法setRouters()設置了路由對象,實現見以下代碼:
1 protected void setRouters(List<Router> routers) { 2 // copy list 3 routers = routers == null ? new ArrayList<Router>() : new ArrayList<Router>(routers); 4 // append url router 5 String routerkey = url.getParameter(Constants.ROUTER_KEY); 6 if (routerkey != null && routerkey.length() > 0) {
//經過SPI實例化配置的路由對象工廠對象 7 RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey); 8 routers.add(routerFactory.getRouter(url)); 9 } 10 // append mock invoker selector 11 routers.add(new MockInvokersSelector()); 12 Collections.sort(routers); 13 this.routers = routers; 14 }
(2)獲得服務對象列表,主要經過doList()實現獲得服務列表,此方法由子類實現,而後依次調用router列表作路由篩選,實現以下:
1 public List<Invoker<T>> list(Invocation invocation) throws RpcException { 2 if (destroyed) { 3 throw new RpcException("Directory already destroyed .url: " + getUrl()); 4 } 5 List<Invoker<T>> invokers = doList(invocation); 6 List<Router> localRouters = this.routers; // local reference 7 if (localRouters != null && localRouters.size() > 0) { 8 for (Router router : localRouters) { 9 try { 10 if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) { 11 invokers = router.route(invokers, getConsumerUrl(), invocation); 12 } 13 } catch (Throwable t) { 14 logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t); 15 } 16 } 17 } 18 return invokers; 19 }
三、StaticDirectory靜態目錄服務,doList()將構造方法中傳入的invoker列表原樣返回。
四、RegistryDirectory 類整合了註冊中心和目錄服務,具體實現實在dubbo-register模塊,我將另外寫一篇博客討論。
6、Dubbo提供了哪些路由機制?如何實現的?
路由機制爲用戶提供了靈活可配置的服務篩選功能,經過用戶自定義配置路由規則,決定一次 dubbo 服務調用的目標服務器。使用場景例如:提供差別化服務,給重要的請求提供性能好的服務器,反之給次要的服務提供性能差的服務器;讀寫分離,根據方法名字add,update,delete等分爲一組,查詢方法分爲一組,分別映射到不一樣的服務器上;對客戶端設置白名單、黑名單;更多場景及具體的應用說明詳見dubbo用戶手冊路由規則篇。
dubbo提供了兩種路由機制:條件路由和腳本路由。
一、向註冊中心寫入路由規則,一般由監控中心或服務治理頁面完成,代碼實現主要在dubb-register模塊:
1 RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension(); 2 Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181")); 3 registry.register(URL.valueOf("condition://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("host = 10.20.153.10 => host = 10.20.153.11") + "));
其中:
condition://
表示路由規則的類型,支持條件路由規則和腳本路由規則,可擴展,必填。0.0.0.0
表示對全部 IP 地址生效,若是隻想對某個 IP 的生效,請填入具體 IP,必填。com.foo.BarService
表示只對指定服務生效,必填。category=routers
表示該數據爲動態配置類型,必填。dynamic=false
表示該數據爲持久數據,當註冊方退出時,數據依然保存在註冊中心,必填。enabled=true
覆蓋規則是否生效,可不填,缺省生效。force=false
當路由結果爲空時,是否強制執行,若是不強制執行,路由結果爲空的路由規則將自動失效,可不填,缺省爲 flase
。runtime=false
是否在每次調用時執行路由規則,不然只在提供者地址列表變動時預先執行並緩存結果,調用時直接從緩存中獲取路由結果。若是用了參數路由,必須設爲 true
,須要注意設置會影響調用的性能,可不填,缺省爲 flase
。priority=1
路由規則的優先級,用於排序,優先級越大越靠前執行,可不填,缺省爲 0
。rule=URL.encode("host = 10.20.153.10 => host = 10.20.153.11")
表示路由規則的內容,必填。
二、條件路由
基於條件表達式的路由規則,如:host = 10.20.153.10 => host = 10.20.153.11。=>左邊的表達式表示消費端信息(matchWhen),右邊的表達式表示服務提供者的服務信息(matchThen)。
代碼實現中,解析=>左右的條件表達式,左側消費端條件表達式解析成when,右側服務端條件表達式解析成then,封裝matchWhen()和matchThen()分別匹配消費端和服務端表達式。
咱們看看源碼如何實現路由邏輯的。
條件路由實現主要靠兩個類,ConditionRouterFactory,實現了ConditionRouter實例化的過程;ConditionRouter,實現了具體的路由機制,其中route方法實現以下,實現主要靠解析和匹配條件表達式。
1 public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) 2 throws RpcException { 3 if (invokers == null || invokers.size() == 0) { 4 return invokers; 5 } 6 try {
//消費端條件不匹配規則,就所有返回 7 if (!matchWhen(url, invocation)) { 8 return invokers; 9 } 10 List<Invoker<T>> result = new ArrayList<Invoker<T>>(); 11 if (thenCondition == null) { 12 logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey()); 13 return result; 14 } 15 for (Invoker<T> invoker : invokers) {
//遍歷invokers,找到匹配規則的invoker(服務端)加入result 16 if (matchThen(invoker.getUrl(), url)) { 17 result.add(invoker); 18 } 19 } 20 if (result.size() > 0) { 21 return result; 22 } else if (force) { //若是結果爲空,force=true,則返回空的列表,不然返回全部的invokers(不執行route規則) 23 logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(Constants.RULE_KEY)); 24 return result; 25 } 26 } catch (Throwable t) { 27 logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t); 28 } 29 return invokers; 30 }
三、腳本路由
經過配置腳本或腳本文件設置路由規則rule,程序編譯執行腳本函數,獲得腳本篩選後的invokers數組。具體邏輯很簡單,就是加載腳本,加載對應腳本引擎,執行腳本,獲得結果後轉換輸出的過程。
1 public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException { 2 try { 3 List<Invoker<T>> invokersCopy = new ArrayList<Invoker<T>>(invokers); 4 Compilable compilable = (Compilable) engine; 5 Bindings bindings = engine.createBindings(); 6 bindings.put("invokers", invokersCopy); 7 bindings.put("invocation", invocation); 8 bindings.put("context", RpcContext.getContext()); 9 CompiledScript function = compilable.compile(rule); 10 Object obj = function.eval(bindings); 11 if (obj instanceof Invoker[]) { 12 invokersCopy = Arrays.asList((Invoker<T>[]) obj); 13 } else if (obj instanceof Object[]) { 14 invokersCopy = new ArrayList<Invoker<T>>(); 15 for (Object inv : (Object[]) obj) { 16 invokersCopy.add((Invoker<T>) inv); 17 } 18 } else { 19 invokersCopy = (List<Invoker<T>>) obj; 20 } 21 return invokersCopy; 22 } catch (ScriptException e) { 23 //fail then ignore rule .invokers. 24 logger.error("route error , rule has been ignored. rule: " + rule + ", method:" + invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(), e); 25 return invokers; 26 } 27 }
7、總結集羣模塊如何帶來高可用、自動發現、可治理的特性?
集羣模塊在dubbo中很是重要,提供了基於RPC功能的高可用特性,此特性主要是經過集羣容錯和負載均衡策略支持的,在服務提供者出現異常時,可配置的容錯特性確保服務能按照配置的策略返回,或切換到其餘服務並重試,或當即拋出異常,知足了不一樣服務不一樣場景的異常後處理須要。可配置的負載均衡策略知足了不一樣場景、需求的服務實現負載均衡,在不一樣場景下均勻的分攤負載,在負載不夠用的狀況下靈活的增長機器節點承擔多餘的負載,保證了集羣的高可用特性。而且能夠靈活配置節點的權重,實現針對不一樣配置的服務節點承擔的請求不一樣。
可配置的路由功能知足了不少場景下的靈活路由需求,如讀寫分離,消費端請求的白名單、黑名單,針對不一樣配置的服務提供給不一樣的消費者等等。因爲可配置性強,能夠針對不一樣的需求作不一樣的配置,可提供很靈活的功能特性。經過配置規則,實現服務降級,能夠屏蔽有問題的服務,並定義服務響應。這些都體現了Dubbo可治理的特性。
目錄服務簡單的定義了對服務地址列表的查詢功能,配合dubb-register註冊中心模塊功能能夠實現服務註冊、自動發現功能。詳見註冊中心模塊的源碼分析章節。
集羣模塊就討論到這裏了,若有不當之處,歡迎你們提出異議和寶貴的意見,這樣有助於我技術上的提高。