1. 簡介
本篇文章,將開始分析 Dubbo 集群容錯(cuò)方面的源碼悍引。集群容錯(cuò)源碼包含四個(gè)部分恩脂,分別是服務(wù)目錄 Directory、服務(wù)路由 Router趣斤、集群 Cluster 和負(fù)載均衡 LoadBalance俩块。這幾個(gè)部分的源碼邏輯相對(duì)比較獨(dú)立,我們將會(huì)分四篇文章進(jìn)行分析浓领。本篇文章作為集群容錯(cuò)的開篇文章玉凯,將和大家一起分析服務(wù)目錄相關(guān)的源碼。在進(jìn)行深入分析之前镊逝,我們先來了解一下服務(wù)目錄是什么壮啊。服務(wù)目錄中存儲(chǔ)了一些和服務(wù)提供者有關(guān)的信息嫉鲸,通過服務(wù)目錄撑蒜,服務(wù)消費(fèi)者可獲取到服務(wù)提供者的信息,比如 ip、端口座菠、服務(wù)協(xié)議等狸眼。通過這些信息,服務(wù)消費(fèi)者就可通過 Netty 等客戶端進(jìn)行遠(yuǎn)程調(diào)用浴滴。在一個(gè)服務(wù)集群中拓萌,服務(wù)提供者數(shù)量并不是一成不變的,如果集群中新增了一臺(tái)機(jī)器升略,相應(yīng)地在服務(wù)目錄中就要新增一條服務(wù)提供者記錄微王。或者品嚣,如果服務(wù)提供者的配置修改了炕倘,服務(wù)目錄中的記錄也要做相應(yīng)的更新。如果這樣說翰撑,服務(wù)目錄和注冊(cè)中心的功能不就雷同了嗎罩旋?確實(shí)如此,這里這么說是為了方便大家理解眶诈。實(shí)際上服務(wù)目錄在獲取注冊(cè)中心的服務(wù)配置信息后涨醋,會(huì)為每條配置信息生成一個(gè) Invoker 對(duì)象,并把這個(gè) Invoker 對(duì)象存儲(chǔ)起來逝撬,這個(gè) Invoker 才是服務(wù)目錄最終持有的對(duì)象浴骂。Invoker 有什么用呢?看名字就知道了宪潮,這是一個(gè)具有遠(yuǎn)程調(diào)用功能的對(duì)象靠闭。講到這大家應(yīng)該知道了什么是服務(wù)目錄了,它可以看做是 Invoker 集合坎炼,且這個(gè)集合中的元素會(huì)隨注冊(cè)中心的變化而進(jìn)行動(dòng)態(tài)調(diào)整愧膀。
關(guān)于服務(wù)目錄這里就先介紹這些,大家先有個(gè)大致印象谣光。接下來我們通過繼承體系圖來了解一下服務(wù)目錄的家族成員都有哪些檩淋。
2. 繼承體系
服務(wù)目錄目前內(nèi)置的實(shí)現(xiàn)有兩個(gè),分別為 StaticDirectory 和 RegistryDirectory萄金,它們均是 AbstractDirectory 的子類蟀悦。AbstractDirectory 實(shí)現(xiàn)了 Directory 接口,這個(gè)接口包含了一個(gè)重要的方法定義氧敢,即 list(Invocation)日戈,用于列舉 Invoker。下面我們來看一下他們的繼承體系圖孙乖。
如上浙炼,Directory 繼承自 Node 接口份氧,Node 這個(gè)接口繼承者比較多,像 Registry弯屈、Monitor蜗帜、Invoker 等均繼承了這個(gè)接口。這個(gè)接口包含了一個(gè)獲取配置信息的方法 getUrl资厉,實(shí)現(xiàn)該接口的類可以向外提供配置信息厅缺。另外,大家注意看 RegistryDirectory 實(shí)現(xiàn)了 NotifyListener 接口宴偿,當(dāng)注冊(cè)中心節(jié)點(diǎn)信息發(fā)生變化后湘捎,RegistryDirectory 可以通過此接口方法得到變更信息,并根據(jù)變更信息動(dòng)態(tài)調(diào)整內(nèi)部 Invoker 列表窄刘。
3. 源碼分析
本章將分析 AbstractDirectory 和它兩個(gè)子類的源碼消痛。AbstractDirectory 封裝了 Invoker 列舉流程,具體的列舉邏輯則由子類實(shí)現(xiàn)都哭,這是典型的模板模式秩伞。所以,接下來我們先來看一下 AbstractDirectory 的源碼欺矫。
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed) {
throw new RpcException("Directory already destroyed...");
}
// 調(diào)用 doList 方法列舉 Invoker纱新,doList 是模板方法,由子類實(shí)現(xiàn)
List<Invoker<T>> invokers = doList(invocation);
// 獲取路由 Router 列表
List<Router> localRouters = this.routers;
if (localRouters != null && !localRouters.isEmpty()) {
for (Router router : localRouters) {
try {
// 獲取 runtime 參數(shù)穆趴,并根據(jù)參數(shù)決定是否進(jìn)行路由
if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
// 進(jìn)行服務(wù)路由
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
} catch (Throwable t) {
logger.error("Failed to execute router: ...");
}
}
}
return invokers;
}
// 模板方法脸爱,由子類實(shí)現(xiàn)
protected abstract List<Invoker<T>> doList(Invocation invocation) throws RpcException;
上面就是 AbstractDirectory 的 list 方法源碼,這個(gè)方法封裝了 Invoker 的列舉過程未妹。如下:
- 調(diào)用 doList 獲取 Invoker 列表
- 根據(jù) Router 的 getUrl 返回值為空與否簿废,以及 runtime 參數(shù)決定是否進(jìn)行服務(wù)路由
以上步驟中,doList 是模板方法络它,需由子類實(shí)現(xiàn)族檬。Router 的 runtime 參數(shù)這里簡單說明一下,這個(gè)參數(shù)決定了是否在每次調(diào)用服務(wù)時(shí)都執(zhí)行路由規(guī)則化戳。如果 runtime 為 true单料,那么每次調(diào)用服務(wù)前,都需要進(jìn)行服務(wù)路由点楼。這個(gè)對(duì)性能造成影響扫尖,配置時(shí)需要注意。
關(guān)于 AbstractDirectory 就分析這么多掠廓,下面開始分析子類的源碼换怖。
3.1 StaticDirectory
StaticDirectory 即靜態(tài)服務(wù)目錄,顧名思義蟀瞧,它內(nèi)部存放的 Invoker 是不會(huì)變動(dòng)的沉颂。所以条摸,理論上它和不可變 List 的功能很相似。下面我們來看一下這個(gè)類的實(shí)現(xiàn)兆览。
public class StaticDirectory<T> extends AbstractDirectory<T> {
// Invoker 列表
private final List<Invoker<T>> invokers;
// 省略構(gòu)造方法
@Override
public Class<T> getInterface() {
// 獲取接口類
return invokers.get(0).getInterface();
}
// 檢測服務(wù)目錄是否可用
@Override
public boolean isAvailable() {
if (isDestroyed()) {
return false;
}
for (Invoker<T> invoker : invokers) {
if (invoker.isAvailable()) {
// 只要有一個(gè) Invoker 是可用的屈溉,就認(rèn)為當(dāng)前目錄是可用的
return true;
}
}
return false;
}
@Override
public void destroy() {
if (isDestroyed()) {
return;
}
// 調(diào)用父類銷毀邏輯
super.destroy();
// 遍歷 Invoker 列表塞关,并執(zhí)行相應(yīng)的銷毀邏輯
for (Invoker<T> invoker : invokers) {
invoker.destroy();
}
invokers.clear();
}
@Override
protected List<Invoker<T>> doList(Invocation invocation) throws RpcException {
// 列舉 Inovker抬探,也就是直接返回 invokers 成員變量
return invokers;
}
}
以上就是 StaticDirectory 的代碼邏輯,很簡單帆赢,就不多說了小压。下面來看看 RegistryDirectory,這個(gè)類的邏輯比較復(fù)雜椰于。
3.2 RegistryDirectory
RegistryDirectory 是一種動(dòng)態(tài)服務(wù)目錄怠益,實(shí)現(xiàn)了 NotifyListener 接口。當(dāng)注冊(cè)中心服務(wù)配置發(fā)生變化后瘾婿,RegistryDirectory 可收到與當(dāng)前服務(wù)相關(guān)的變化蜻牢。收到變更通知后,RegistryDirectory 可根據(jù)配置變更信息刷新 Invoker 列表偏陪。RegistryDirectory 中有幾個(gè)比較重要的邏輯抢呆,第一是 Invoker 的列舉邏輯,第二是接收服務(wù)配置變更的邏輯笛谦,第三是 Invoker 列表的刷新邏輯抱虐。接下來按順序?qū)@三塊邏輯。
3.2.1 列舉 Invoker
Invoker 列舉邏輯封裝在 doList 方法中饥脑,相關(guān)代碼如下:
public List<Invoker<T>> doList(Invocation invocation) {
if (forbidden) {
// 服務(wù)提供者關(guān)閉或禁用了服務(wù)恳邀,此時(shí)拋出 No provider 異常
throw new RpcException(RpcException.FORBIDDEN_EXCEPTION,
"No provider available from registry ...");
}
List<Invoker<T>> invokers = null;
// 獲取 Invoker 本地緩存
Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap;
if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
// 獲取方法名和參數(shù)列表
String methodName = RpcUtils.getMethodName(invocation);
Object[] args = RpcUtils.getArguments(invocation);
// 檢測參數(shù)列表的第一個(gè)參數(shù)是否為 String 或 enum 類型
if (args != null && args.length > 0 && args[0] != null
&& (args[0] instanceof String || args[0].getClass().isEnum())) {
// 通過 方法名 + 第一個(gè)參數(shù)名稱 查詢 Invoker 列表,具體的使用場景暫時(shí)沒想到
invokers = localMethodInvokerMap.get(methodName + "." + args[0]);
}
if (invokers == null) {
// 通過方法名獲取 Invoker 列表
invokers = localMethodInvokerMap.get(methodName);
}
if (invokers == null) {
// 通過星號(hào) * 獲取 Invoker 列表
invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
}
// 冗余邏輯灶轰,pull request #2861 移除了下面的 if 分支代碼
if (invokers == null) {
Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();
if (iterator.hasNext()) {
invokers = iterator.next();
}
}
}
// 返回 Invoker 列表
return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;
}
以上代碼進(jìn)行多次嘗試谣沸,以期從 localMethodInvokerMap 中獲取到 Invoker 列表。一般情況下笋颤,普通的調(diào)用可通過方法名獲取到對(duì)應(yīng)的 Invoker 列表鳄抒,泛化調(diào)用可通過 ***** 獲取到 Invoker 列表。localMethodInvokerMap 源自 RegistryDirectory 類的成員變量 methodInvokerMap椰弊。doList 方法可以看做是對(duì) methodInvokerMap 變量的讀操作许溅,至于對(duì) methodInvokerMap 變量的寫操作,下一節(jié)進(jìn)行分析秉版。
3.2.2 接收服務(wù)變更通知
RegistryDirectory 是一個(gè)動(dòng)態(tài)服務(wù)目錄贤重,會(huì)隨注冊(cè)中心配置的變化進(jìn)行動(dòng)態(tài)調(diào)整。因此 RegistryDirectory 實(shí)現(xiàn)了 NotifyListener 接口清焕,通過這個(gè)接口獲取注冊(cè)中心變更通知并蝗。下面我們來看一下具體的邏輯祭犯。
public synchronized void notify(List<URL> urls) {
// 定義三個(gè)集合,分別用于存放服務(wù)提供者 url滚停,路由 url沃粗,配置器 url
List<URL> invokerUrls = new ArrayList<URL>();
List<URL> routerUrls = new ArrayList<URL>();
List<URL> configuratorUrls = new ArrayList<URL>();
for (URL url : urls) {
String protocol = url.getProtocol();
// 獲取 category 參數(shù)
String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
// 根據(jù) category 參數(shù)將 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)) {
// 添加服務(wù)提供者 url
invokerUrls.add(url);
} else {
// 忽略不支持的 category
logger.warn("Unsupported category ...");
}
}
if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
// 將 url 轉(zhuǎn)成 Configurator
this.configurators = toConfigurators(configuratorUrls);
}
if (routerUrls != null && !routerUrls.isEmpty()) {
// 將 url 轉(zhuǎn)成 Router
List<Router> routers = toRouters(routerUrls);
if (routers != null) {
setRouters(routers);
}
}
List<Configurator> 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 方法首先是根據(jù) url 的 category 參數(shù)對(duì) url 進(jìn)行分門別類存儲(chǔ)键畴,然后通過 toRouters 和 toConfigurators 將 url 列表轉(zhuǎn)成 Router 和 Configurator 列表最盅。最后調(diào)用 refreshInvoker 方法刷新 Invoker 列表。這里的 toRouters 和 toConfigurators 方法邏輯不復(fù)雜起惕,大家自行分析涡贱。接下來,我們把重點(diǎn)放在 refreshInvoker 方法上惹想。
3.2.3 刷新 Invoker 列表
refreshInvoker 方法是保證 RegistryDirectory 隨注冊(cè)中心變化而變化的關(guān)鍵所在问词。這一塊邏輯比較多,接下來一一進(jìn)行分析嘀粱。
private void refreshInvoker(List<URL> invokerUrls) {
// invokerUrls 僅有一個(gè)元素激挪,且 url 協(xié)議頭為 empty,此時(shí)表示禁用所有服務(wù)
if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
&& Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
// 設(shè)置 forbidden 為 true
this.forbidden = true;
this.methodInvokerMap = null;
// 銷毀所有 Invoker
destroyAllInvokers();
} else {
this.forbidden = false;
Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap;
if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
// 添加緩存 url 到 invokerUrls 中
invokerUrls.addAll(this.cachedInvokerUrls);
} else {
this.cachedInvokerUrls = new HashSet<URL>();
// 緩存 invokerUrls
this.cachedInvokerUrls.addAll(invokerUrls);
}
if (invokerUrls.isEmpty()) {
return;
}
// 將 url 轉(zhuǎn)成 Invoker
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);
// 將 newUrlInvokerMap 轉(zhuǎn)成方法名到 Invoker 列表的映射
Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap);
// 轉(zhuǎn)換出錯(cuò)锋叨,直接打印異常垄分,并返回
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
logger.error(new IllegalStateException("urls to invokers error ..."));
return;
}
// 合并多個(gè)組的 Invoker
this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
this.urlInvokerMap = newUrlInvokerMap;
try {
// 銷毀無用 Invoker
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap);
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}
}
}
refreshInvoker 方法首先會(huì)根據(jù)入?yún)?invokerUrls 的數(shù)量和協(xié)議頭判斷是否禁用所有的服務(wù),如果禁用悲柱,則將 forbidden 設(shè)為 true锋喜,并銷毀所有的 Invoker。若不禁用豌鸡,則將 url 轉(zhuǎn)成 Invoker嘿般,得到 <url, Invoker> 的映射關(guān)系。然后進(jìn)一步進(jìn)行轉(zhuǎn)換涯冠,得到 <methodName, Invoker 列表> 映射關(guān)系炉奴。之后進(jìn)行多組 Invoker 合并操作,并將合并結(jié)果賦值給 methodInvokerMap蛇更。methodInvokerMap 變量在 doList 方法中會(huì)被用到瞻赶,doList 會(huì)對(duì)該變量進(jìn)行讀操作,在這里是寫操作派任。當(dāng)新的 Invoker 列表生成后砸逊,還要一個(gè)重要的工作要做,就是銷毀無用的 Invoker掌逛,避免服務(wù)消費(fèi)者調(diào)用已下線的服務(wù)的服務(wù)师逸。
接下來對(duì) refreshInvoker 方法中涉及到的調(diào)用一一進(jìn)行分析。按照順序豆混,先來分析 url 到 Invoker 的轉(zhuǎn)換過程篓像。
private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
if (urls == null || urls.isEmpty()) {
return newUrlInvokerMap;
}
Set<String> keys = new HashSet<String>();
// 獲取服務(wù)消費(fèi)端配置的協(xié)議
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(",");
// 檢測服務(wù)提供者協(xié)議是否被服務(wù)消費(fèi)者所支持
for (String acceptProtocol : acceptProtocols) {
if (providerUrl.getProtocol().equals(acceptProtocol)) {
accept = true;
break;
}
}
if (!accept) {
// 若服務(wù)消費(fèi)者協(xié)議頭不被消費(fèi)者所支持动知,則忽略當(dāng)前 providerUrl
continue;
}
}
// 忽略 empty 協(xié)議
if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
continue;
}
// 通過 SPI 檢測服務(wù)端協(xié)議是否被消費(fèi)端支持,不支持則拋出異常
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)) {
// 忽略重復(fù) url
continue;
}
keys.add(key);
// 將本地 Invoker 緩存賦值給 localUrlInvokerMap
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
// 獲取與 url 對(duì)應(yīng)的 Invoker
Invoker<T> 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 {
// 獲取 enable 配置,并賦值給 enable 變量
enabled = url.getParameter(Constants.ENABLED_KEY, true);
}
if (enabled) {
// 調(diào)用 refer 獲取 Invoker
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
}
} catch (Throwable t) {
logger.error("Failed to refer invoker for interface...");
}
if (invoker != null) {
// 緩存 Invoker 實(shí)例
newUrlInvokerMap.put(key, invoker);
}
// 緩存命中
} else {
// 將 invoker 存儲(chǔ)到 newUrlInvokerMap 中
newUrlInvokerMap.put(key, invoker);
}
}
keys.clear();
return newUrlInvokerMap;
}
toInvokers 方法一開始會(huì)對(duì)服務(wù)提供者 url 進(jìn)行檢測奠滑,若服務(wù)消費(fèi)端的配置不支持服務(wù)端的協(xié)議丹皱,或服務(wù)端 url 協(xié)議頭為 empty 時(shí),toInvokers 均會(huì)忽略服務(wù)提供方 url养叛。必要的檢測做完后种呐,緊接著是合并 url宰翅,然后訪問緩存弃甥,嘗試獲取與 url 對(duì)應(yīng)的 invoker。如果緩存命中汁讼,直接將 Invoker 存入 newUrlInvokerMap 中即可淆攻。如果未命中,則需新建 Invoker嘿架。
toInvokers 方法返回的是 <url, Invoker> 映射關(guān)系表瓶珊,接下來還要對(duì)這個(gè)結(jié)果進(jìn)行進(jìn)一步處理,得到方法名到 Invoker 列表的映射關(guān)系耸彪。這個(gè)過程由 toMethodInvokers 方法完成伞芹,如下:
private Map<String, List<Invoker<T>>> toMethodInvokers(Map<String, Invoker<T>> invokersMap) {
// 方法名 -> Invoker 列表
Map<String, List<Invoker<T>>> newMethodInvokerMap = new HashMap<String, List<Invoker<T>>>();
List<Invoker<T>> invokersList = new ArrayList<Invoker<T>>();
if (invokersMap != null && invokersMap.size() > 0) {
for (Invoker<T> invoker : invokersMap.values()) {
// 獲取 methods 參數(shù)
String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY);
if (parameter != null && parameter.length() > 0) {
// 切分 methods 參數(shù)值,得到方法名數(shù)組
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)) {
// 根據(jù)方法名獲取 Invoker 列表
List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
if (methodInvokers == null) {
methodInvokers = new ArrayList<Invoker<T>>();
newMethodInvokerMap.put(method, methodInvokers);
}
// 存儲(chǔ) Invoker 到列表中
methodInvokers.add(invoker);
}
}
}
}
invokersList.add(invoker);
}
}
// 進(jìn)行服務(wù)級(jí)別路由蝉娜,參考 pull request #749
List<Invoker<T>> newInvokersList = route(invokersList, null);
// 存儲(chǔ) <*, newInvokersList> 映射關(guān)系
newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList);
if (serviceMethods != null && serviceMethods.length > 0) {
for (String method : serviceMethods) {
List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
if (methodInvokers == null || methodInvokers.isEmpty()) {
methodInvokers = newInvokersList;
}
// 進(jìn)行方法級(jí)別路由
newMethodInvokerMap.put(method, route(methodInvokers, method));
}
}
// 排序唱较,轉(zhuǎn)成不可變列表
for (String method : new HashSet<String>(newMethodInvokerMap.keySet())) {
List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
Collections.sort(methodInvokers, InvokerComparator.getComparator());
newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers));
}
return Collections.unmodifiableMap(newMethodInvokerMap);
}
上面方法主要做了三件事情, 第一是對(duì)入?yún)⑦M(jìn)行遍歷召川,然后從 Invoker 的 url 成員變量中獲取 methods 參數(shù)南缓,并切分成數(shù)組。隨后以方法名為鍵荧呐,Invoker 列表為值汉形,將映射關(guān)系存儲(chǔ)到 newMethodInvokerMap 中。第二是分別基于類和方法對(duì) Invoker 列表進(jìn)行路由操作倍阐。第三是對(duì) Invoker 列表進(jìn)行排序概疆,并轉(zhuǎn)成不可變列表。關(guān)于 toMethodInvokers 方法就先分析到這峰搪,我們繼續(xù)向下分析岔冀,這次要分析的多組服務(wù)的合并邏輯。
private Map<String, List<Invoker<T>>> toMergeMethodInvokerMap(Map<String, List<Invoker<T>>> methodMap) {
Map<String, List<Invoker<T>>> result = new HashMap<String, List<Invoker<T>>>();
// 遍歷入?yún)? for (Map.Entry<String, List<Invoker<T>>> entry : methodMap.entrySet()) {
String method = entry.getKey();
List<Invoker<T>> invokers = entry.getValue();
// group -> Invoker 列表
Map<String, List<Invoker<T>>> groupMap = new HashMap<String, List<Invoker<T>>>();
// 遍歷 Invoker 列表
for (Invoker<T> invoker : invokers) {
// 獲取分組配置
String group = invoker.getUrl().getParameter(Constants.GROUP_KEY, "");
List<Invoker<T>> groupInvokers = groupMap.get(group);
if (groupInvokers == null) {
groupInvokers = new ArrayList<Invoker<T>>();
// 緩存 <group, List<Invoker>> 到 groupMap 中
groupMap.put(group, groupInvokers);
}
// 存儲(chǔ) invoker 到 groupInvokers
groupInvokers.add(invoker);
}
if (groupMap.size() == 1) {
// 如果 groupMap 中僅包含一組鍵值對(duì)罢艾,此時(shí)直接取出該鍵值對(duì)的值即可
result.put(method, groupMap.values().iterator().next());
// groupMap.size() > 1 成立楣颠,表示 groupMap 中包含多組鍵值對(duì)尽纽,比如:
// {
// "dubbo": [invoker1, invoker2, invoker3, ...],
// "hello": [invoker4, invoker5, invoker6, ...]
// }
} else if (groupMap.size() > 1) {
List<Invoker<T>> groupInvokers = new ArrayList<Invoker<T>>();
for (List<Invoker<T>> groupList : groupMap.values()) {
// 通過集群類合并每個(gè)分組對(duì)應(yīng)的 Invoker 列表
groupInvokers.add(cluster.join(new StaticDirectory<T>(groupList)));
}
// 緩存結(jié)果
result.put(method, groupInvokers);
} else {
result.put(method, invokers);
}
}
return result;
}
上面方法首先是生成 group 到 Invoker 類比的映射關(guān)系表,若關(guān)系表中的映射關(guān)系數(shù)量大于1童漩,表示有多組服務(wù)边翁。此時(shí)通過集群類合并每組 Invoker潦刃,并將合并結(jié)果存儲(chǔ)到 groupInvokers 中。之后將方法名與 groupInvokers 存到到 result 中,并返回暖混,整個(gè)邏輯結(jié)束。
接下來我們?cè)賮砜匆幌?Invoker 列表刷新邏輯的最后一個(gè)動(dòng)作 — 刪除無用 Invoker硼被。如下:
private void destroyUnusedInvokers(Map<String, Invoker<T>> oldUrlInvokerMap, Map<String, Invoker<T>> newUrlInvokerMap) {
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
destroyAllInvokers();
return;
}
List<String> deleted = null;
if (oldUrlInvokerMap != null) {
// 獲取新生成的 Invoker 列表
Collection<Invoker<T>> newInvokers = newUrlInvokerMap.values();
// 遍歷老的 <url, Invoker> 映射表
for (Map.Entry<String, Invoker<T>> entry : oldUrlInvokerMap.entrySet()) {
// 檢測 newInvokers 中是否包含老的 Invoker
if (!newInvokers.contains(entry.getValue())) {
if (deleted == null) {
deleted = new ArrayList<String>();
}
// 若不包含食棕,則將老的 Invoker 對(duì)應(yīng)的 url 存入 deleted 列表中
deleted.add(entry.getKey());
}
}
}
if (deleted != null) {
// 遍歷 deleted 集合,并到老的 <url, Invoker> 映射關(guān)系表查出 Invoker馁痴,銷毀之
for (String url : deleted) {
if (url != null) {
// 從 oldUrlInvokerMap 中移除 url 對(duì)應(yīng)的 Invoker
Invoker<T> invoker = oldUrlInvokerMap.remove(url);
if (invoker != null) {
try {
// 銷毀 Invoker
invoker.destroy();
} catch (Exception e) {
logger.warn("destroy invoker...");
}
}
}
}
}
}
destroyUnusedInvokers 方法的主要邏輯是通過 newUrlInvokerMap 找出待刪除 Invoker 對(duì)應(yīng)的 url谊娇,并將 url 存入到 deleted 列表中。然后再遍歷 deleted 列表罗晕,并從 oldUrlInvokerMap 中移除相應(yīng)的 Invoker济欢,銷毀之。整個(gè)邏輯大致如此小渊,不是很難理解法褥。
到此關(guān)于 Invoker 列表的刷新邏輯就分析了,這里對(duì)整個(gè)過程進(jìn)行簡單總結(jié)酬屉。如下:
- 檢測入?yún)⑹欠駜H包含一個(gè) url半等,且 url 協(xié)議頭為 empty
- 若第一步檢測結(jié)果為 true,表示禁用所有服務(wù)呐萨,此時(shí)銷毀所有的 Invoker
- 若第一步檢測結(jié)果為 false杀饵,此時(shí)將入?yún)⑥D(zhuǎn)為 Invoker 列表
- 對(duì)將上一步邏輯生成的結(jié)果進(jìn)行進(jìn)一步處理,得到方法名到 Invoker 的映射關(guān)系表
- 合并多組 Invoker
- 銷毀無用 Invoker
Invoker 的刷新邏輯還是比較復(fù)雜的垛吗,大家在看的過程中多寫點(diǎn) demo 進(jìn)行調(diào)試凹髓,以加深理解。
4. 總結(jié)
本篇文章對(duì) Dubbo 服務(wù)目錄進(jìn)行了較為詳細(xì)的分析怯屉,篇幅主要集中在 RegistryDirectory 的源碼分析上蔚舀。從代碼量上可以看出,想讓本地服務(wù)目錄和注冊(cè)中心保持一致還是需要做很多事情的锨络,并不簡單赌躺。服務(wù)目錄是 Dubbo 集群容錯(cuò)的一部分,也是比較基礎(chǔ)的部分羡儿,所以大家應(yīng)盡量搞懂礼患。
附原文地址http://dubbo.apache.org/zh-cn/docs/source_code_guide/directory.html