摘要:在一個服務集群中,服務提供者數量并不是一成不變的,如果集群中新增了一臺機器,相應地在服務目錄中就要新增一條服務提供者記錄。
1. 簡介
前面文章分析了服務的導出與引用過程,從本篇文章開始,我將開始分析 Dubbo 集群容錯方面的源碼。這部分源碼包含四個部分,分別是服務目錄 Directory、服務路由 Router、集群 Cluster 和負載均衡 LoadBalance。這幾個部分的源碼邏輯比較獨立,我會分四篇文章進行分析。本篇文章作為集群容錯的開篇文章,將和大家一起分析服務目錄相關的源碼。在進行深入分析之前,我們先來了解一下服務目錄是什么。服務目錄中存儲了一些和服務提供者有關的信息,通過服務目錄,服務消費者可獲取到服務提供者的信息,比如 ip、端口、服務協議等。通過這些信息,服務消費者就可通過 Netty 等客戶端進行遠程調用。在一個服務集群中,服務提供者數量并不是一成不變的,如果集群中新增了一臺機器,相應地在服務目錄中就要新增一條服務提供者記錄。或者,如果服務提供者的配置修改了,服務目錄中的記錄也要做相應的更新。如果這樣說,服務目錄和注冊中心的功能不就雷同了嗎。確實如此,這里這么說是為了方便大家理解。實際上服務目錄在獲取注冊中心的服務配置信息后,會為每條配置信息生成一個 Invoker 對象,并把這個 Invoker 對象存儲起來,這個 Invoker 才是服務目錄最終持有的對象。Invoker 有什么用呢?看名字就知道了,這是一個具有遠程調用功能的對象。講到這大家應該知道了什么是服務目錄了,它可以看做是 Invoker 集合,且這個集合中的元素會隨注冊中心的變化而進行動態調整。
好了,關于服務目錄這里就先介紹這些,大家先有個大致印象即可。接下來我們通過繼承體系圖來了解一下服務目錄的家族成員都有哪些。
2. 繼承體系服務目錄目前內置的實現有兩個,分別為 StaticDirectory 和 RegistryDirectory,它們均是 AbstractDirectory 的子類。AbstractDirectory 實現了 Directory 接口,這個接口包含了一個重要的方法定義,即 list(Invocation),用于列舉 Invoker。下面我們來看一下他們的繼承體系圖。
如上,Directory 繼承自 Node 接口,Node 這個接口繼承者比較多,像 Registry、Monitor、Invoker 等繼承了這個接口。這個接口包含了一個獲取配置信息的方法 getUrl,實現該接口的類可以向外提供配置信息。另外,大家注意看 RegistryDirectory 實現了 NotifyListener 接口,當注冊中心節點信息發生變化后,RegistryDirectory 可以通過此接口方法得到變更信息,并根據變更信息動態調整內部 Invoker 列表。
現在大家對服務目錄的繼承體系應該比較清楚了,下面我們深入到源碼中,探索服務目錄是如何實現的。
3. 源碼分析本章我將分析 AbstractDirectory 和它兩個子類的源碼。這里之所以要分析 AbstractDirectory,而不是直接分析子類是有一定原因的。AbstractDirectory 封裝了 Invoker 列舉流程,具體的列舉邏輯則由子類實現,這是典型的模板模式。所以,接下來我們先來看一下 AbstractDirectory 的源碼。
public List> list(Invocation invocation) throws RpcException { if (destroyed) { throw new RpcException("Directory already destroyed..."); } // 調用 doList 方法列舉 Invoker,這里的 doList 是模板方法,由子類實現 List > invokers = doList(invocation); // 獲取路由器 List localRouters = this.routers; if (localRouters != null && !localRouters.isEmpty()) { for (Router router : localRouters) { try { // 獲取 runtime 參數,并根據參數決定是否進行路由 if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) { // 進行服務路由 invokers = router.route(invokers, getConsumerUrl(), invocation); } } catch (Throwable t) { logger.error("Failed to execute router: ..."); } } } return invokers; } // 模板方法,由子類實現 protected abstract List > doList(Invocation invocation) throws RpcException;
上面就是 AbstractDirectory 的 list 方法源碼,這個方法封裝了 Invoker 的列舉過程。如下:
調用 doList 獲取 Invoker 列表
根據 Router 的 getUrl 返回值為空與否,以及 runtime 參數決定是否進行服務路由
以上步驟中,doList 是模板方法,需由子類實現。Router 的 runtime 參數這里簡單說明一下,這個參數決定了是否在每次調用服務時都執行路由規則。如果 runtime 為 true,那么每次調用服務前,都需要進行服務路由。這個對性能造成影響,慎重配置。關于該參數更詳細的說明,請參考官方文檔。
介紹完 AbstractDirectory,接下來我們開始分析子類的源碼。
3.1 StaticDirectoryStaticDirectory 即靜態服務目錄,顧名思義,它內部存放的 Invoker 是不會變動的。所以,理論上它和不可變 List 的功能很相似。下面我們來看一下這個類的實現。
public class StaticDirectoryextends AbstractDirectory { // Invoker 列表 private final List > invokers; // 省略構造方法 @Override public Class getInterface() { // 獲取接口類 return invokers.get(0).getInterface(); } // 檢測服務目錄是否可用 @Override public boolean isAvailable() { if (isDestroyed()) { return false; } for (Invoker invoker : invokers) { if (invoker.isAvailable()) { // 只要有一個 Invoker 是可用的,就任務當前目錄是可用的 return true; } } return false; } @Override public void destroy() { if (isDestroyed()) { return; } // 調用父類銷毀邏輯 super.destroy(); // 遍歷 Invoker 列表,并執行相應的銷毀邏輯 for (Invoker invoker : invokers) { invoker.destroy(); } invokers.clear(); } @Override protected List > doList(Invocation invocation) throws RpcException { // 列舉 Inovker,也就是直接返回 invokers 成員變量 return invokers; } }
以上就是 StaticDirectory 的代碼邏輯,很簡單,大家都能看懂,我就不多說了。下面來看看 RegistryDirectory,這個類的邏輯比較復雜。
3.2 RegistryDirectoryRegistryDirectory 是一種動態服務目錄,它實現了 NotifyListener 接口。當注冊中心服務配置發生變化后,RegistryDirectory 可收到與當前服務相關的變化。收到變更通知后,RegistryDirectory 可根據配置變更信息刷新 Invoker 列表。RegistryDirectory 中有幾個比較重要的邏輯,第一是 Invoker 的列舉邏輯,第二是接受服務配置變更的邏輯,第三是 Invoker 的刷新邏輯。接下來,我將按順序對這三塊邏輯。
3.2.1 列舉 InvokerInvoker 列舉邏輯封裝在 doList 方法中,這是個模板方法,前面已經介紹過了。那這里就不過多啰嗦了,我們直入主題吧。
public List> doList(Invocation invocation) { if (forbidden) { // 服務提供者關閉或禁用了服務,此時拋出 No provider 異常 throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry ..."); } List > invokers = null; // 獲取 Invoker 本地緩存 Map >> localMethodInvokerMap = this.methodInvokerMap; if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) { // 獲取方法名和參數列表 String methodName = RpcUtils.getMethodName(invocation); Object[] args = RpcUtils.getArguments(invocation); // 檢測參數列表的第一個參數是否為 String 或 enum 類型 if (args != null && args.length > 0 && args[0] != null && (args[0] instanceof String || args[0].getClass().isEnum())) { // 通過 方法名 + 第一個參數名稱 查詢 Invoker 列表,具體的使用場景暫時沒想到 invokers = localMethodInvokerMap.get(methodName + "." + args[0]); } if (invokers == null) { // 通過方法名獲取 Invoker 列表 invokers = localMethodInvokerMap.get(methodName); } if (invokers == null) { // 通過星號 * 獲取 Invoker 列表 invokers = localMethodInvokerMap.get(Constants.ANY_VALUE); } if (invokers == null) { Iterator >> iterator = localMethodInvokerMap.values().iterator(); if (iterator.hasNext()) { // 通過迭代器獲取 Invoker 列表 invokers = iterator.next(); } } } // 返回 Invoker 列表 return invokers == null ? new ArrayList
>(0) : invokers; }
以上代碼進行多次嘗試,以期從 localMethodInvokerMap 中獲取到 Invoker 列表。一般情況下,普通的調用可通過方法名獲取到對應的 Invoker 列表,泛化調用可通過 獲取到 Invoker 列表。按現有的邏輯,不管什么情況下, 到 Invoker 列表的映射關系 <*, invokers> 總是存在的,也就意味著 localMethodInvokerMap.get(Constants.ANY_VALUE) 總是有值返回。除非這個值是 null,才會通過通過迭代器獲取 Invoker 列表。至于什么情況下為空,我暫時未完全搞清楚,我猜測是被路由規則(用戶可基于 Router 接口實現自定義路由器)處理后,可能會得到一個 null。目前僅是猜測,未做驗證。
本節的邏輯主要是從 localMethodInvokerMap 中獲取 Invoker,localMethodInvokerMap 源自 RegistryDirectory 類的成員變量 methodInvokerMap。doList 方法可以看做是對 methodInvokerMap 變量的讀操作,至于對 methodInvokerMap 變量的寫操作,這個將在后續進行分析。
3.2.2 接收服務變更通知RegistryDirectory 是一個動態服務目錄,它需要接受注冊中心配置進行動態調整。因此 RegistryDirectory 實現了 NotifyListener 接口,通過這個接口獲取注冊中心變更通知。下面我們來看一下具體的邏輯。
public synchronized void notify(Listurls) { // 定義三個集合,分別用于存放服務提供者 url,路由 url,配置器 url List invokerUrls = new ArrayList (); List routerUrls = new ArrayList (); List configuratorUrls = new ArrayList (); for (URL url : urls) { String protocol = url.getProtocol(); // 獲取 category 參數 String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); // 根據 category 參數將 url 分別放到不同的列表中 if (Constants.ROUTERS_CATEGORY.equals(category) || Constants.ROUTE_PROTOCOL.equals(protocol)) { // 添加路由器 url routerUrls.add(url); } else if (Constants.CONFIGURATORS_CATEGORY.equals(category) || Constants.OVERRIDE_PROTOCOL.equals(protocol)) { // 添加配置器 url configuratorUrls.add(url); } else if (Constants.PROVIDERS_CATEGORY.equals(category)) { // 添加服務提供者 url invokerUrls.add(url); } else { // 忽略不支持的 category logger.warn("Unsupported category ..."); } } if (configuratorUrls != null && !configuratorUrls.isEmpty()) { // 將 url 轉成 Configurator this.configurators = toConfigurators(configuratorUrls); } if (routerUrls != null && !routerUrls.isEmpty()) { // 將 url 轉成 Router List routers = toRouters(routerUrls); if (routers != null) { setRouters(routers); } } List localConfigurators = this.configurators; this.overrideDirectoryUrl = directoryUrl; if (localConfigurators != null && !localConfigurators.isEmpty()) { for (Configurator configurator : localConfigurators) { // 配置 overrideDirectoryUrl this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl); } } // 刷新 Invoker 列表 refreshInvoker(invokerUrls); }
如上,notify 方法首先是根據 url 的 category 參數對 url 進行分門別類存儲,然后通過 toRouters 和 toConfigurators 將 url 列表轉成 Router 和 Configurator 列表。最后調用 refreshInvoker 方法刷新 Invoker 列表。這里的 toRouters 和 toConfigurators 方法邏輯不復雜,大家自行分析。接下來,我們把重點放在 refreshInvoker 方法上。
3.2.3 刷新 Invoker 列表接著上一節繼續分析,refreshInvoker 方法是保證 RegistryDirectory 隨注冊中心變化而變化的關鍵所在。這一塊邏輯比較多,接下來一一進行分析。
private void refreshInvoker(ListinvokerUrls) { // invokerUrls 僅有一個元素,且 url 協議頭為 empty,此時表示禁用所有服務 if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) { // 設置 forbidden 為 true this.forbidden = true; this.methodInvokerMap = null; // 銷毀所有 Invoker destroyAllInvokers(); } else { this.forbidden = false; Map > oldUrlInvokerMap = this.urlInvokerMap; if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) { // 添加緩存 url 到 invokerUrls 中 invokerUrls.addAll(this.cachedInvokerUrls); } else { this.cachedInvokerUrls = new HashSet (); // 緩存 invokerUrls this.cachedInvokerUrls.addAll(invokerUrls); } if (invokerUrls.isEmpty()) { return; } // 將 url 轉成 Invoker Map > newUrlInvokerMap = toInvokers(invokerUrls); // 將 newUrlInvokerMap 轉成方法名到 Invoker 列表的映射 Map >> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 轉換出錯,直接打印異常,并返回 if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) { logger.error(new IllegalStateException("urls to invokers error ...")); return; } // 合并多個組的 Invoker this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap; // 保存為本地緩存 this.urlInvokerMap = newUrlInvokerMap; try { // 銷毀無用 Invoker destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); } catch (Exception e) { logger.warn("destroyUnusedInvokers error. ", e); } } }
上面方法的代碼不是很多,但是邏輯卻不少。首先時根據入參 invokerUrls 的數量和協議頭判斷是否禁用所有的服務,如果禁用,則將 forbidden 設為 true,并銷毀所有的 Invoker。若不禁用,則將 url 轉成 Invoker,得到
接下里,我將對上面涉及到的調用進行分析。按照順序,這里先來分析 url 到 Invoker 的轉換過程。
private Map> toInvokers(List urls) { Map > newUrlInvokerMap = new HashMap >(); if (urls == null || urls.isEmpty()) { return newUrlInvokerMap; } Set keys = new HashSet (); // 獲取服務消費端配置的協議 String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY); for (URL providerUrl : urls) { if (queryProtocols != null && queryProtocols.length() > 0) { boolean accept = false; String[] acceptProtocols = queryProtocols.split(","); // 檢測服務提供者協議是否被服務消費者所支持 for (String acceptProtocol : acceptProtocols) { if (providerUrl.getProtocol().equals(acceptProtocol)) { accept = true; break; } } if (!accept) { // 若服務消費者協議頭不被消費者所支持,則忽略當前 providerUrl continue; } } // 忽略 empty 協議 if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) { continue; } // 通過 SPI 檢測服務端協議是否被消費端支持 if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) { logger.error(new IllegalStateException("Unsupported protocol...")); continue; } // 合并 url URL url = mergeUrl(providerUrl); String key = url.toFullString(); if (keys.contains(key)) { // 忽略重復 url continue; } keys.add(key); // 本地 Invoker 緩存列表 Map > localUrlInvokerMap = this.urlInvokerMap; Invoker invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key); // 緩存未命中 if (invoker == null) { try { boolean enabled = true; if (url.hasParameter(Constants.DISABLED_KEY)) { // 獲取 disable 配置,并修改 enable 變量 enabled = !url.getParameter(Constants.DISABLED_KEY, false); } else { enabled = url.getParameter(Constants.ENABLED_KEY, true); } if (enabled) { // 調用 refer 獲取 Invoker invoker = new InvokerDelegate (protocol.refer(serviceType, url), url, providerUrl); } } catch (Throwable t) { logger.error("Failed to refer invoker for interface..."); } if (invoker != null) { // 緩存 Invoker 實例 newUrlInvokerMap.put(key, invoker); } } else { // 緩存命中,將 invoker 存儲到 newUrlInvokerMap 中 newUrlInvokerMap.put(key, invoker); } } keys.clear(); return newUrlInvokerMap; }
toInvokers 方法一開始會對服務提供者 url 進行檢測,若服務消費端的配置不支持服務端的協議,或服務端 url 協議頭為 empty 時,toInvokers 均會忽略服務提供方 url。必要的檢測做完后,緊接著是合并 url,然后訪問緩存,嘗試獲取與 url 對應的 invoker。如果緩存命中,直接將 Invoker 存入 newUrlInvokerMap 中即可。如果未命中,則需要新建 Invoker。Invoker 是通過 Protocol 的 refer 方法創建的,這個我在上一篇文章中已經分析過了,這里就不贅述了。
toInvokers 方法返回的是
private Map>> toMethodInvokers(Map > invokersMap) { // 方法名 -> Invoker 列表 Map >> newMethodInvokerMap = new HashMap >>(); List > invokersList = new ArrayList >(); if (invokersMap != null && invokersMap.size() > 0) { for (Invoker invoker : invokersMap.values()) { // 獲取 methods 參數 String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY); if (parameter != null && parameter.length() > 0) { // 切分 methods 參數值,得到方法名數組 String[] methods = Constants.COMMA_SPLIT_PATTERN.split(parameter); if (methods != null && methods.length > 0) { for (String method : methods) { // 方法名不為 * if (method != null && method.length() > 0 && !Constants.ANY_VALUE.equals(method)) { // 根據方法名獲取 Invoker 列表 List > methodInvokers = newMethodInvokerMap.get(method); if (methodInvokers == null) { methodInvokers = new ArrayList >(); newMethodInvokerMap.put(method, methodInvokers); } // 存儲 Invoker 到列表中 methodInvokers.add(invoker); } } } } invokersList.add(invoker); } } // 進行服務級別路由,參考:https://github.com/apache/incubator-dubbo/pull/749 List > newInvokersList = route(invokersList, null); // 存儲 <*, newInvokersList> 映射關系 newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList); if (serviceMethods != null && serviceMethods.length > 0) { for (String method : serviceMethods) { List > methodInvokers = newMethodInvokerMap.get(method); if (methodInvokers == null || methodInvokers.isEmpty()) { methodInvokers = newInvokersList; } // 進行方法級別路由 newMethodInvokerMap.put(method, route(methodInvokers, method)); } } // 排序,轉成不可變列表 for (String method : new HashSet (newMethodInvokerMap.keySet())) { List > methodInvokers = newMethodInvokerMap.get(method); Collections.sort(methodInvokers, InvokerComparator.getComparator()); newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers)); } return Collections.unmodifiableMap(newMethodInvokerMap); }
上面方法主要做了三件事情, 第一是對入參進行遍歷,然后獲取 methods 參數,并切分成數組。隨后以方法名為鍵,Invoker 列表為值,將映射關系存儲到 newMethodInvokerMap 中。第二是分別基于類和方法對 Invoker 列表進行路由操作。第三是對 Invoker 列表進行排序,并轉成不可變列表。關于 toMethodInvokers 方法就先分析到這,我們繼續向下分析,這次要分析的多組服務的合并邏輯。
private Map>> toMergeMethodInvokerMap(Map >> methodMap) { Map >> result = new HashMap >>(); // 遍歷入參 for (Map.Entry >> entry : methodMap.entrySet()) { String method = entry.getKey(); List > invokers = entry.getValue(); // group -> Invoker 列表 Map >> groupMap = new HashMap >>(); // 遍歷 Invoker 列表 for (Invoker invoker : invokers) { // 獲取分組配置 String group = invoker.getUrl().getParameter(Constants.GROUP_KEY, ""); List > groupInvokers = groupMap.get(group); if (groupInvokers == null) { groupInvokers = new ArrayList >(); // 緩存 > 到 groupMap 中 groupMap.put(group, groupInvokers); } // 存儲 invoker 到 groupInvokers groupInvokers.add(invoker); } if (groupMap.size() == 1) { // 如果 groupMap 中僅包含一組鍵值對,此時直接取出該鍵值對的值即可 result.put(method, groupMap.values().iterator().next()); // groupMap 中包含多組鍵值對,比如: // { // "dubbo": [invoker1, invoker2, invoker3, ...], // "hello": [invoker4, invoker5, invoker6, ...] // } } else if (groupMap.size() > 1) { List > groupInvokers = new ArrayList >(); for (List > groupList : groupMap.values()) { // 通過集群類合并每個分組對應的 Invoker 列表 groupInvokers.add(cluster.join(new StaticDirectory (groupList))); } // 緩存結果 result.put(method, groupInvokers); } else { result.put(method, invokers); } } return result; }
上面方法首先是生成 group 到 Invoker 類比的映射關系表,若關系表中的映射關系數量大于1,表示有多組服務。此時通過集群類合并每組 Invoker,并將合并結果存儲到 groupInvokers 中。之后將方法名與 groupInvokers 存到到 result 中,并返回,整個邏輯結束。
接下來我們再來看一下 Invoker 列表刷新邏輯的最后一個動作 -- 刪除無用 Invoker。如下:
private void destroyUnusedInvokers(Map> oldUrlInvokerMap, Map > newUrlInvokerMap) { if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) { destroyAllInvokers(); return; } List deleted = null; if (oldUrlInvokerMap != null) { // 獲取新生成的 Invoker 列表 Collection > newInvokers = newUrlInvokerMap.values(); // 遍歷老的 映射表 for (Map.Entry > entry : oldUrlInvokerMap.entrySet()) { // 檢測 newInvokers 中是否包含老的 Invoker if (!newInvokers.contains(entry.getValue())) { if (deleted == null) { deleted = new ArrayList (); } // 若不包含,則將老的 Invoker 對應的 url 存入 deleted 列表中 deleted.add(entry.getKey()); } } } if (deleted != null) { // 遍歷 deleted 集合,并到老的 映射關系表查出 Invoker,銷毀之 for (String url : deleted) { if (url != null) { // 從 oldUrlInvokerMap 中移除 url 對應的 Invoker Invoker invoker = oldUrlInvokerMap.remove(url); if (invoker != null) { try { // 銷毀 Invoker invoker.destroy(); } catch (Exception e) { logger.warn("destroy invoker..."); } } } } } }
destroyUnusedInvokers 方法的主要邏輯是通過 newUrlInvokerMap 找出待刪除 Invoker 對應的 url,并將 url 存入到 deleted 列表中。然后再遍歷 deleted 列表,并從 oldUrlInvokerMap 中移除相應的 Invoker,銷毀之。整個邏輯大致如此,不是很難理解。
到此關于 Invoker 列表的刷新邏輯就分析了,這里對整個過程進行簡單總結。如下:
檢測入參是否僅包含一個 url,且 url 協議頭為 empty
若第一步檢測結果為 true,表示禁用所有服務,此時銷毀所有的 Invoker
若第一步檢測結果為 false,此時將入參轉為 Invoker 列表
對將上一步邏輯刪除的結果進行進一步處理,得到方法名到 Invoker 的映射關系表
合并多組 Invoker
銷毀無用 Invoker
Invoker 的刷新邏輯還是比較復雜的,大家在看的過程中多寫點 demo 進行調試。好了,本節就到這。
4. 總結本篇文章對 Dubbo 服務目錄進行了較為詳細的分析,篇幅主要集中在 RegistryDirectory 的源碼分析上。分析下來,不由得感嘆,想讓本地服務目錄和注冊中心保持一致還是需要做很多事情的,并不簡單。服務目錄是 Dubbo 集群容錯的一部分,也是比較基礎的部分,所以大家務必搞懂。
好了,本篇文章就先到這了。感謝大家閱讀。
本文在知識共享許可協議 4.0 下發布,轉載需在明顯位置處注明出處
作者:田小波
本文同步發布在我的個人博客:http://www.tianxiaobo.com
本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72243.html
摘要:集群用途是將多個服務提供者合并為一個,并將這個暴露給服務消費者。比如發請求,接受服務提供者返回的數據等。如果包含,表明對應的服務提供者可能因網絡原因未能成功提供服務。如果不包含,此時還需要進行可用性檢測,比如檢測服務提供者網絡連通性等。 1.簡介 為了避免單點故障,現在的應用至少會部署在兩臺服務器上。對于一些負載比較高的服務,會部署更多臺服務器。這樣,同一環境下的服務提供者數量會大于1...
摘要:源碼分析條件路由規則有兩個條件組成,分別用于對服務消費者和提供者進行匹配。如果服務提供者匹配條件為空,表示對某些服務消費者禁用服務。此時第六次循環分隔符,,。第二個和第三個參數來自方法的參數列表,這兩個參數分別為服務提供者和服務消費者。 1. 簡介 上一篇文章分析了集群容錯的第一部分 -- 服務目錄 Directory。服務目錄在刷新 Invoker 列表的過程中,會通過 Router...
摘要:失敗安全,出現異常時,直接忽略。失敗自動恢復,在調用失敗后,返回一個空結果給服務提供者。源碼分析一該類實現了接口,是集群的抽象類。 集群——cluster 目標:介紹dubbo中集群容錯的幾種模式,介紹dubbo-cluster下support包的源碼。 前言 集群容錯還是很好理解的,就是當你調用失敗的時候所作出的措施。先來看看有哪些模式: showImg(https://segmen...
摘要:即服務提供者目前正在處理的請求數一個請求對應一條連接最少,表明該服務提供者效率高,單位時間內可處理更多的請求。此時應優先將請求分配給該服務提供者。初始情況下,所有服務提供者活躍數均為。 1.簡介 LoadBalance 中文意思為負載均衡,它的職責是將網絡請求,或者其他形式的負載均攤到不同的機器上。避免集群中部分服務器壓力過大,而另一些服務器比較空閑的情況。通過負載均衡,可以讓每臺服務...
摘要:上一篇源碼解析概要篇中我們了解到中的一些概念及消費端總體調用過程。由于在生成代理實例的時候,在構造函數中賦值了,因此可以只用該進行方法的調用。 上一篇 dubbo源碼解析——概要篇中我們了解到dubbo中的一些概念及消費端總體調用過程。本文中,將進入消費端源碼解析(具體邏輯會放到代碼的注釋中)。本文先是對消費過程的總體代碼邏輯理一遍,個別需要細講的點,后面會專門的文章進行解析。...
閱讀 2621·2021-11-25 09:43
閱讀 2725·2021-11-04 16:09
閱讀 1634·2021-10-12 10:13
閱讀 881·2021-09-29 09:35
閱讀 880·2021-08-03 14:03
閱讀 1777·2019-08-30 15:55
閱讀 2989·2019-08-28 18:14
閱讀 3489·2019-08-26 13:43