-
先看官網(wǎng)兩張圖【引用來(lái)自官網(wǎng)】:
image.png
暴露服務(wù)的入口自然就清楚了
官網(wǎng)說(shuō)明:
- 1.首先 ServiceConfig 類(lèi)拿到對(duì)外提供服務(wù)的實(shí)際類(lèi) ref(如:HelloWorldImpl),然后通過(guò) ProxyFactory 類(lèi)的 getInvoker 方法使用 ref 生成一個(gè) AbstractProxyInvoker 實(shí)例,到這一步就完成具體服務(wù)到 Invoker 的轉(zhuǎn)化。接下來(lái)就是 Invoker 轉(zhuǎn)換到 Exporter 的過(guò)程。
- 2.Dubbo 處理服務(wù)暴露的關(guān)鍵就在 Invoker 轉(zhuǎn)換到 Exporter 的過(guò)程,上圖中的紅色部分。下面我們以 Dubbo 和 RMI 這兩種典型協(xié)議的實(shí)現(xiàn)來(lái)進(jìn)行說(shuō)明:
一.概覽
- 1.暴露主要入口在ServiceBean,該bean實(shí)現(xiàn)了spring相關(guān)的接口,主要關(guān)注下InitializingBean和ApplicationListener,那也就說(shuō)明在容器啟動(dòng)初始化完成之后會(huì)收到容器發(fā)送的監(jiān)聽(tīng)通知,進(jìn)而執(zhí)行監(jiān)聽(tīng)方法,也就是在這個(gè)監(jiān)聽(tīng)方法里實(shí)現(xiàn)了服務(wù)的暴露。
- 2.拿到容器的bean后,也就是ref指向的那個(gè)配置bean或者@Sevice配置的那個(gè)bean,反正就是給spring托管的bean;進(jìn)而將其封裝成invoker;
- 3.拿到invoker之后,就將其轉(zhuǎn)成Exporter,這里當(dāng)然就要緩存起來(lái),緩存的key就是invoker的url了,至于什么是url,后面就清楚了
- 4.拿到Exporter后就啟動(dòng)Server服務(wù),開(kāi)啟端口,請(qǐng)求來(lái)到時(shí),根據(jù)請(qǐng)求信息生成key,到緩存查找Exporter,找到Invoker完成調(diào)用。
- 5.這里會(huì)很奇怪,此處沒(méi)有注冊(cè)中心,簡(jiǎn)單點(diǎn)認(rèn)知:其實(shí)注冊(cè)中心也就管理下地址并進(jìn)行變更時(shí)通知而已;脫離了注冊(cè)中心,Exporter照樣暴露服務(wù)開(kāi)啟端口等待調(diào)用。
二.容器初始化
當(dāng)容器初始化OK,ContextRefreshEvent觸發(fā)監(jiān)聽(tīng)事件,ServiceBean執(zhí)行onApplicationEvent事 件方法,進(jìn)而進(jìn)行export。
此處其實(shí)利用了上篇講的SPI擴(kuò)展,ServiceConfig初始化時(shí),首先會(huì)先初始化靜態(tài)變量protocol和proxyFactory,這兩個(gè)變量的初始化就是通過(guò)dubbo的spi擴(kuò)展機(jī)制得到的,因此此處可以提前跟蹤下代碼就理解了,此處不再做筆錄。
protocol擴(kuò)展出來(lái)的類(lèi)形態(tài)是這樣的
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
//根據(jù)URL配置信息獲取Protocol協(xié)議,默認(rèn)是dubbo
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
//根據(jù)協(xié)議名,獲取Protocol的實(shí)現(xiàn)
//獲得Protocol的實(shí)現(xiàn)過(guò)程中,會(huì)對(duì)Protocol先進(jìn)行依賴注入,然后進(jìn)行Wrapper包裝,最后返回被修改過(guò)的Protocol
//包裝經(jīng)過(guò)了ProtocolFilterWrapper,ProtocolListenerWrapper,RegistryProtocol
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
}
-
proxyFactory擴(kuò)展出來(lái)的類(lèi)形態(tài)是這樣的:
package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.extension.ExtensionLoader; public class ProxyFactory$Adpative implements com.alibaba.dubbo.rpc.ProxyFactory { public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws java.lang.Object { if (arg2 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg2; String extName = url.getParameter("proxy", "javassist"); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])"); com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName); return extension.getInvoker(arg0, arg1, arg2); } public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker { if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = url.getParameter("proxy", "javassist"); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])"); com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName); return extension.getProxy(arg0); } }
看下具體擴(kuò)展的是哪個(gè)類(lèi)吧
/** * ProxyFactory. (API/SPI, Singleton, ThreadSafe) */ @SPI("javassist") public interface ProxyFactory { /** * create proxy. * * @param invoker * @return proxy */ @Adaptive({Constants.PROXY_KEY}) <T> T getProxy(Invoker<T> invoker) throws RpcException; /** * create invoker. * * @param <T> * @param proxy * @param type * @param url * @return invoker */ @Adaptive({Constants.PROXY_KEY}) <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException; }
默認(rèn)的處理配置很明顯是javassist,后面debug就可以方便打斷點(diǎn)了。
同時(shí)跟著上面生成的類(lèi)描述,后面用到的時(shí)候跟這這樣的代碼執(zhí)行邏輯就比較好跟蹤,不然執(zhí)行debug有點(diǎn)蒙。
二.服務(wù)暴露
1.暴露原理
- 處理過(guò)程大概分為以下幾個(gè)主要點(diǎn),官網(wǎng)圖只是個(gè)大致流程:
- 1.反復(fù)檢查及準(zhǔn)備相關(guān)環(huán)境配置
- 2.加載相關(guān)的注冊(cè)中心,這里充當(dāng)注冊(cè)中心的就多了
- 3.暴露本地服務(wù)
- 4.暴露遠(yuǎn)程服務(wù)(此處啟動(dòng)netty,打開(kāi)端口)
- 5.連接注冊(cè)中心并注冊(cè)
- 6.監(jiān)聽(tīng)注冊(cè)中心
按照上面的步驟詳細(xì)筆錄一下
- ServiceBean監(jiān)聽(tīng)事件觸發(fā)時(shí)執(zhí)行了下面方法:
/***
* 就比如監(jiān)聽(tīng)spring容器初始化完成
* 沒(méi)有設(shè)置延遲或者延遲為-1,dubbo會(huì)在Spring實(shí)例化完bean之后,在刷新容器最后一步發(fā)布ContextRefreshEvent事件的時(shí)候,通知實(shí)現(xiàn)了ApplicationListener的類(lèi)進(jìn)行回調(diào)onApplicationEvent,dubbo會(huì)在這個(gè)方法中發(fā)布服務(wù)。
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
顯然有一個(gè)延遲配置選項(xiàng),延遲是否配置也會(huì)影響這里的入口,沒(méi)有延遲時(shí)的入口就是這個(gè)監(jiān)聽(tīng),設(shè)置了延遲時(shí)入口就是afterPropertySet();其實(shí)最后的重點(diǎn)都是export();
-
2.export()
/** * 暴露服務(wù) */ public synchronized void export() { if (provider != null) { if (export == null) { export = provider.getExport(); } if (delay == null) { delay = provider.getDelay(); } } if (export != null && !export) { return; } if (delay != null && delay > 0) { delayExportExecutor.schedule(new Runnable() { @Override public void run() { doExport(); } }, delay, TimeUnit.MILLISECONDS); } else { doExport(); } }
這個(gè)方法沒(méi)啥,直接看最后一行doExport好了。
/** * 先執(zhí)行一系列的檢查方法,然后調(diào)用doExportUrls方法。 * 檢查方法會(huì)檢測(cè)dubbo的配置是否在Spring配置文件中聲明,沒(méi)有的話讀取properties文件初始化。 */ protected synchronized void doExport() { ... checkApplication(); checkRegistry(); checkProtocol(); appendProperties(this); checkStubAndMock(interfaceClass); if (path == null || path.length() == 0) { path = interfaceName; } // 重點(diǎn) doExportUrls(); ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref); ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel); }
很顯然這個(gè)方法前面省略的那一大坨就是檢查環(huán)境配置及準(zhǔn)備環(huán)境配置了,重點(diǎn)依然在最后doExportUrls();
-
doExportUrls()
@SuppressWarnings({"unchecked", "rawtypes"}) private void doExportUrls() { /** 獲取所有的注冊(cè)中心url */ List<URL> registryURLs = loadRegistries(true); for (ProtocolConfig protocolConfig : protocols) { doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }
這里有個(gè)點(diǎn)很有意思:就是這里會(huì)去加載所有的注冊(cè)中心,為啥會(huì)這么做呢,看看我們的配置:
image.png
因?yàn)槲覀冿@示指明了registry這一配置,跟下代碼如何處理:
```
protected List<URL> loadRegistries(boolean provider) {
checkRegistry();
List<URL> registryList = new ArrayList<URL>();
if (registries != null && !registries.isEmpty()) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (address == null || address.length() == 0) {
address = Constants.ANYHOST_VALUE;
}
String sysaddress = System.getProperty("dubbo.registry.address");
if (sysaddress != null && sysaddress.length() > 0) {
address = sysaddress;
}
if (address.length() > 0 && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
appendParameters(map, application);
appendParameters(map, config);
map.put("path", RegistryService.class.getName());
map.put("dubbo", Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
if (!map.containsKey("protocol")) {
if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
map.put("protocol", "remote");
} else {
map.put("protocol", "dubbo");
}
}
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
|| (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
```
直接進(jìn)來(lái)就是這樣的效果,很顯然對(duì)于在配置中指定了registry屬性的,會(huì)在加載spring BeanDefinition的時(shí)候就加載了注冊(cè)中心。
返回的注冊(cè)中心URL是這樣的:
后面重點(diǎn)來(lái)了:doExportUrlsFor1Protocol 這里就區(qū)分協(xié)議進(jìn)行服務(wù)的暴露了
-
doExportUrlsFor1Protocol
下面代碼有點(diǎn)長(zhǎng),還是直接copy出來(lái),因?yàn)橛行┳⑨屩苯涌吹谋容^方便/** * 服務(wù)發(fā)布 -- 遠(yuǎn)程暴露 & 本地暴露 * 本地暴露是暴露在JVM中,不需要網(wǎng)絡(luò)通信. * 遠(yuǎn)程暴露是將ip,端口等信息暴露給遠(yuǎn)程客戶端,調(diào)用時(shí)需要網(wǎng)絡(luò)通信. * * 根據(jù)不同的協(xié)議將服務(wù)以URL形式暴露。如果scope配置為none則不暴露,如果服務(wù)未配置成remote,則本地暴露exportLocal,如果未配置成local,則注冊(cè)服務(wù)registryProcotol。 */ private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { ... // don't export when none is configured if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) { /** 根據(jù)配置選擇暴露服務(wù)方式 */ // export to local if the config is not remote (export to remote only when config is remote) if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) { /** 本地暴露 **/ exportLocal(url); } /** 遠(yuǎn)程暴露 */ // export to remote if the config is not local (export to local only when config is local) if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) { if (logger.isInfoEnabled()) { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); } if (registryURLs != null && !registryURLs.isEmpty()) { for (URL registryURL : registryURLs) { url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY)); URL monitorUrl = loadMonitor(registryURL); if (monitorUrl != null) { url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString()); } if (logger.isInfoEnabled()) { logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL); } Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker); exporters.add(exporter); } } else { Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker); exporters.add(exporter); } } } this.urls.add(url); }
既然是RPC,遠(yuǎn)程暴露必然無(wú)疑,那為啥會(huì)有本地暴露? 其實(shí)解釋很簡(jiǎn)單,自己調(diào)用自己難道也要走網(wǎng)不成,一個(gè)JVM的速度跟處理比網(wǎng)絡(luò)肯定要好吧。
前面一大段都是都是根據(jù)配置為服務(wù)最終暴露做準(zhǔn)備,不多說(shuō),后面沒(méi)省略的才是重點(diǎn)代碼直接進(jìn)入本地暴露和遠(yuǎn)程暴露
2.本地暴露
-
exportLocal(URL url):
@SuppressWarnings({"unchecked", "rawtypes"}) private void exportLocal(URL url) { if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { URL local = URL.valueOf(url.toFullString()) .setProtocol(Constants.LOCAL_PROTOCOL) .setHost(LOCALHOST) .setPort(0); ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref)); /** // 1.首先還是先獲得Invoker // 2.然后導(dǎo)出成Exporter,并緩存 // 3.這里的proxyFactory實(shí)際是JavassistProxyFactory **/ Exporter<?> exporter = protocol.export(proxyFactory.getInvoker(ref, (Class) interfaceClass, local)); exporters.add(exporter); logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry"); } }
debug到這個(gè)url,它的值是這樣的:
image.png
跟注釋中講的一樣,做了這幾件事:
- 1.獲得Invoker
- 2.導(dǎo)出成Exporter,并緩存
這里的proxyFactory實(shí)際是JavassistProxyFactory前面已經(jīng)講過(guò)為啥了。那就看看getInvoker做了什么?
/** * JavaassistRpcProxyFactory * * 讓用戶像以本地調(diào)用方式調(diào)用遠(yuǎn)程服務(wù),就必須使用代理,然后說(shuō)到動(dòng)態(tài)代理,一般我們就想到兩種,一種是JDK的動(dòng)態(tài)代理,一種是CGLIB的動(dòng)態(tài)代理,那我們看看兩者有什么特點(diǎn). * 1.JDK的動(dòng)態(tài)代理代理的對(duì)象必須要實(shí)現(xiàn)一個(gè)接口,而針對(duì)于沒(méi)有接口的類(lèi),則可用CGLIB. * 2.CGLIB其原理也很簡(jiǎn)單,對(duì)指定的目標(biāo)類(lèi)生成一個(gè)子類(lèi),并覆蓋其中方法實(shí)現(xiàn)增強(qiáng),但由于采用的是繼承,所以不能對(duì)final修飾的類(lèi)進(jìn)行代理. * 3.除了以上兩種大家都很熟悉的方式外,其實(shí)還有一種方式,就是javassist生成字節(jié)碼來(lái)實(shí)現(xiàn)代理 */ public class JavassistProxyFactory extends AbstractProxyFactory { @Override @SuppressWarnings("unchecked") public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) { return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); } @Override public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // TODO Wrapper cannot handle this scenario correctly: the classname contains '$' Wrapper類(lèi)不能處理帶$的類(lèi)名 /** * 1.首先對(duì)實(shí)現(xiàn)類(lèi)做一個(gè)包裝,生成一個(gè)包裝后的類(lèi)。 * 2.然后新創(chuàng)建一個(gè)Invoker實(shí)例,這個(gè)Invoker中包含著生成的Wrapper類(lèi),Wrapper類(lèi)中有具體的實(shí)現(xiàn)類(lèi) */ final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); return new AbstractProxyInvoker<T>(proxy, type, url) { /** * 返回一個(gè)Invoker實(shí)例,doInvoke方法中直接返回上面wrapper的invokeMethod * 關(guān)于生成的wrapper,請(qǐng)看下面列出的生成的代碼,其中invokeMethod方法中就有實(shí)現(xiàn)類(lèi)對(duì)實(shí)際方法的調(diào)用 */ @Override protected Object doInvoke(T proxy, String methodName,Class<?>[] parameterTypes,Object[] arguments) throws Throwable { return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; } }
注釋很清楚,返回一個(gè)包裝類(lèi)的Invoker,里面包裝的是具體的實(shí)現(xiàn)類(lèi),執(zhí)行邏輯當(dāng)然也是通過(guò)包裝調(diào)用到具體邏輯了。
獲取到invoker就執(zhí)行生成Expoter了。
這里稍微有點(diǎn)復(fù)雜,export鏈?zhǔn)沁@樣的:ProtocolListenerWrapper-->ProtocolFilterWrapper-->InjvmProtocol
中間過(guò)程大致為相關(guān)的過(guò)濾操作,看到最后的一個(gè)InjvmExporter
/** * InjvmExporter */ class InjvmExporter<T> extends AbstractExporter<T> { private final String key; private final Map<String, Exporter<?>> exporterMap; /** * 利用exporterMap緩存了exporter, */ InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) { super(invoker); this.key = key; this.exporterMap = exporterMap; exporterMap.put(key, this); } @Override public void unexport() { super.unexport(); exporterMap.remove(key); } }
生成了Exporter并緩存,用下面的debug結(jié)束本地暴露。
image.png
image.png
3.遠(yuǎn)程暴露
-
1.獲取Invoker過(guò)程省略,跟本地暴露基本一致。依然用的JavassistProxyFactory,這里也貼出來(lái)JdkProxyFactory,做個(gè)比較。
public class JdkProxyFactory extends AbstractProxyFactory { @Override @SuppressWarnings("unchecked") public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) { return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker)); } @Override public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { return new AbstractProxyInvoker<T>(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable { Method method = proxy.getClass().getMethod(methodName, parameterTypes); return method.invoke(proxy, arguments); } }; } }
-
2.export過(guò)程
image.png
image.png可以debug看到ProtocolListenerWrapper跟ProtocolFilterWrapper對(duì)于Registry類(lèi)型的Invoker不做任何處理,直接調(diào)用具體協(xié)議進(jìn)行處理。
-
3.進(jìn)入RegistryProtocol
/** * 暴露服務(wù) */ @Override public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { // export invoker // 這里就交給了具體的協(xié)議去暴露服務(wù) final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker); URL registryUrl = getRegistryUrl(originInvoker); //registry provider --> /** 注冊(cè)開(kāi)始 鏈接註冊(cè)中心 eg: zookeeper */ //registry provider //根據(jù)invoker中的url獲取Registry實(shí)例 //并且連接到注冊(cè)中心 //此時(shí)提供者作為消費(fèi)者引用注冊(cè)中心核心服務(wù)RegistryService final Registry registry = getRegistry(originInvoker); /** 注冊(cè)到注冊(cè)中心的URL */ final URL registedProviderUrl = getRegistedProviderUrl(originInvoker); //to judge to delay publish whether or not boolean register = registedProviderUrl.getParameter("register", true); /** 將originInvoker加入本地緩存 */ ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl); if (register) { //調(diào)用遠(yuǎn)端注冊(cè)中心的register方法進(jìn)行服務(wù)注冊(cè) //若有消費(fèi)者訂閱此服務(wù),則推送消息讓消費(fèi)者引用此服務(wù)。 //注冊(cè)中心緩存了所有提供者注冊(cè)的服務(wù)以供消費(fèi)者發(fā)現(xiàn)。 register(registryUrl, registedProviderUrl); ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true); } // Subscribe the override data // FIXME 提供者訂閱時(shí),會(huì)影響同一JVM即暴露服務(wù)又引用統(tǒng)一服務(wù)的場(chǎng)景,因?yàn)閟ubscribed以服務(wù)名為緩存的key,導(dǎo)致訂閱信息覆蓋 // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover. /** 一旦registedProviderUrl有變化,就重新組裝返回URL,否則返回原來(lái)值 */ final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl); /** 構(gòu)造服務(wù)變化通知Listener */ final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); /** 將Listener緩存 */ overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); /** 為更新本地的zookeeper信息緩存文件,而發(fā)起的訂閱請(qǐng)求 //提供者向注冊(cè)中心訂閱所有注冊(cè)服務(wù)的覆蓋配置*/ registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); // 保證每次export都返回一個(gè)新的export實(shí)例 // 返回暴露后的Exporter給上層ServiceConfig進(jìn)行緩存,便于后期撤銷(xiāo)暴露。 //Ensure that a new exporter instance is returned every time export return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl); }
看注釋很明顯做了這幾件事:
- 1.交給具體的協(xié)議去暴露服務(wù),也就是開(kāi)啟服務(wù),打開(kāi)端口,等待請(qǐng)求了
- 2.獲取注冊(cè)中心地址URl,并連接到注冊(cè)中心
- 3.注冊(cè)自己到注冊(cè)中心
- 4.獲取配置覆蓋地址,設(shè)置監(jiān)聽(tīng)器進(jìn)行覆蓋監(jiān)聽(tīng)
- 5.訂閱覆蓋配置
- 6.返回暴露的Exporter
-
3.1 交給具體協(xié)議進(jìn)行服務(wù)暴露:doLocalExport(originInvoker)
繼續(xù)跟蹤@SuppressWarnings("unchecked") private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) { String key = getCacheKey(originInvoker); ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key); if (exporter == null) { synchronized (bounds) { exporter = (ExporterChangeableWrapper<T>) bounds.get(key); if (exporter == null) { //得到一個(gè)Invoker代理,里面包含原來(lái)的Invoker final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker)); //此處protocol還是最上面生成的代碼,調(diào)用代碼中的export方法,會(huì)根據(jù)協(xié)議名選擇調(diào)用具體的實(shí)現(xiàn)類(lèi) //這里我們需要調(diào)用DubboProtocol的export方法 //這里的使用具體協(xié)議進(jìn)行導(dǎo)出的invoker是個(gè)代理invoker //導(dǎo)出完之后,返回一個(gè)新的ExporterChangeableWrapper實(shí)例 exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker); bounds.put(key, exporter); } } } return exporter; }
先把生成的invoker包裝成InvokerDelegete代理,交由擴(kuò)展的protocol去暴露
image.png
看暴露邏輯
image.png
image.png
很明顯這里要經(jīng)過(guò)invoker鏈的構(gòu)造。構(gòu)造完之后進(jìn)入到具體協(xié)議的暴露了。這里有個(gè)點(diǎn)需要說(shuō)明下:最ServiceConfig中Export進(jìn)來(lái)的時(shí)候是invoker是register型,所以這里不做處理,在這里會(huì)將具體的dubbo url提取出來(lái)再經(jīng)過(guò)鏈路構(gòu)造暴露,所以這里會(huì)進(jìn)行相關(guān)處理了,就是下面這行代碼。final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
進(jìn)入具體協(xié)議暴露:DubboProtocol
-
3.2 具體協(xié)議暴露服務(wù)
/** * 暴露服務(wù) */ @Override public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { URL url = invoker.getUrl(); // export service. // key由serviceName,port,version,group組成 // 當(dāng)nio客戶端發(fā)起遠(yuǎn)程調(diào)用時(shí),nio服務(wù)端通過(guò)此key來(lái)決定調(diào)用哪個(gè)Exporter,也就是執(zhí)行的Invoker。 // dubbo.common.hello.service.HelloService:20880 String key = serviceKey(url); //將Invoker轉(zhuǎn)換成Exporter //直接new一個(gè)新實(shí)例 //沒(méi)做啥處理,就是做一些賦值操作 //這里的exporter就包含了invoker DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap); // 緩存要暴露的服務(wù),key是上面生成的 exporterMap.put(key, exporter); //export an stub service for dispatching event //是否支持本地存根 //遠(yuǎn)程服務(wù)后,客戶端通常只剩下接口,而實(shí)現(xiàn)全在服務(wù)器端, //但提供方有些時(shí)候想在客戶端也執(zhí)行部分邏輯,比如:做ThreadLocal緩存, //提前驗(yàn)證參數(shù),調(diào)用失敗后偽造容錯(cuò)數(shù)據(jù)等等,此時(shí)就需要在API中帶上Stub, //客戶端生成Proxy實(shí)例,會(huì)把Proxy通過(guò)構(gòu)造函數(shù)傳給Stub, //然后把Stub暴露組給用戶,Stub可以決定要不要去調(diào)Proxy。 Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT); Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false); if (isStubSupportEvent && !isCallbackservice) { String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY); if (stubServiceMethods == null || stubServiceMethods.length() == 0) { if (logger.isWarnEnabled()) { logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) + "], has set stubproxy support event ,but no stub methods founded.")); } } else { stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods); } } /** 打開(kāi)socket,供調(diào)用方調(diào)用 -> 根據(jù)URL綁定IP與端口,建立NIO框架的Server*/ openServer(url); optimizeSerialization(url); /** 遠(yuǎn)程服務(wù)結(jié)束 */ return exporter; }
注釋已經(jīng)解釋了做了哪些事情,很清楚。存根的用處借用官網(wǎng)一張圖及配置實(shí)現(xiàn)說(shuō)明下:
image.png
具體配置:
image.png
提供實(shí)現(xiàn):
package com.foo;
public class BarServiceStub implements BarService {
private final BarService barService;
// 構(gòu)造函數(shù)傳入真正的遠(yuǎn)程代理對(duì)象
public (BarService barService) {
this.barService = barService;
}
public String sayHello(String name) {
// 此代碼在客戶端執(zhí)行, 你可以在客戶端做ThreadLocal本地緩存,或預(yù)先驗(yàn)證參數(shù)是否合法,等等
try {
return barService.sayHello(name);
} catch (Exception e) {
// 你可以容錯(cuò),可以做任何AOP攔截事項(xiàng)
return "容錯(cuò)數(shù)據(jù)";
}
}
}
還是回到暴露的主體:
- 1.獲得Exporter并緩存
- 2.打開(kāi)socket,供調(diào)用方調(diào)用,即:根據(jù)URL綁定IP與端口,建立NIO框架的Server
看第二點(diǎn):
/** 創(chuàng)建NIO Server進(jìn)行監(jiān)聽(tīng) */
private void openServer(URL url) {
// find server.
// key是IP:PORT
// 192.168.110.197:20880
String key = url.getAddress();
// client 也可以暴露一個(gè)只有server可以調(diào)用的服務(wù)
// client can export a service which's only for server to invoke
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
//同一JVM中,同協(xié)議的服務(wù),共享同一個(gè)Server,
//第一個(gè)暴露服務(wù)的時(shí)候創(chuàng)建server,
//以后相同協(xié)議的服務(wù)都使用同一個(gè)server
if (server == null) {
serverMap.put(key, createServer(url));
} else {
// server支持reset,配合override功能使用
// server supports reset, use together with override
//同協(xié)議的服務(wù)后來(lái)暴露服務(wù)的則使用第一次創(chuàng)建的同一Server
//server支持reset,配合override功能使用
//accept、idleTimeout、threads、heartbeat參數(shù)的變化會(huì)引起Server的屬性發(fā)生變化
//這時(shí)需要重新設(shè)置Server
server.reset(url);
}
}
}
劃重點(diǎn):createServer(URL url)
private ExchangeServer createServer(URL url) {
// 默認(rèn)開(kāi)啟server關(guān)閉時(shí)發(fā)送readonly事件
// send readonly event when server closes, it's enabled by default
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
// 開(kāi)啟默認(rèn)的heartbeat
// enable heartbeat by default
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
ExchangeServer server;
try {
//Exchangers是門(mén)面類(lèi),里面封裝的是Exchanger的邏輯。
//Exchanger默認(rèn)只有一個(gè)實(shí)現(xiàn)HeaderExchanger.
//Exchanger負(fù)責(zé)數(shù)據(jù)交換和網(wǎng)絡(luò)通信。
//從Protocol進(jìn)入Exchanger,標(biāo)志著程序進(jìn)入了remote層。
//這里requestHandler是ExchangeHandlerAdapter
// 封裝信息轉(zhuǎn)換,Dubbo的Exchanger層
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
str = url.getParameter(Constants.CLIENT_KEY);
if (str != null && str.length() > 0) {
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return server;
}
/**
* 封裝請(qǐng)求響應(yīng)模式,同步轉(zhuǎn)異步
* getExchanger方法根據(jù)url獲取到一個(gè)默認(rèn)的實(shí)現(xiàn)HeaderExchanger
* 調(diào)用HeaderExchanger的bind方法
*/
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
//getExchanger方法根據(jù)url獲取到一個(gè)默認(rèn)的實(shí)現(xiàn)HeaderExchanger
//調(diào)用HeaderExchanger的bind方法
return getExchanger(url).bind(url, handler);
}
最后進(jìn)入到這里
/**
* doOpen方法創(chuàng)建Netty的Server端并打開(kāi),具體的事情就交給Netty去處理了
*/
@Override
protected void doOpen() throws Throwable {
NettyHelper.setNettyLoggerFactory();
//boss線程池
ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
//worker線程池
ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
//ChannelFactory,沒(méi)有指定工作者線程數(shù)量,就使用cpu+1
ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
bootstrap = new ServerBootstrap(channelFactory);
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
channels = nettyHandler.getChannels();
// https://issues.jboss.org/browse/NETTY-365
// https://issues.jboss.org/browse/NETTY-379
// final Timer timer = new HashedWheelTimer(new NamedThreadFactory("NettyIdleTimer", true));
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
ChannelPipeline pipeline = Channels.pipeline();
/*int idleTimeout = getIdleTimeout();
if (idleTimeout > 10000) {
pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));
}*/
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
// bind
channel = bootstrap.bind(getBindAddress());
}
到這里服務(wù)暴露邏輯就完成了,完了就可以接受請(qǐng)求并進(jìn)行處理了。下面是注冊(cè)到注冊(cè)中心了
-
3.3 注冊(cè)
回到RegistryProtocol的export方法
image.png
image.png
貼代碼
@Override public void register(URL url) { super.register(url); failedRegistered.remove(url); failedUnregistered.remove(url); try { /** 向服務(wù)器發(fā)起注冊(cè)請(qǐng)求 調(diào)用子類(lèi)具體實(shí)現(xiàn),發(fā)送注冊(cè)請(qǐng)求*/ // Sending a registration request to the server side doRegister(url); } catch (Exception e) { Throwable t = e; /** 如果開(kāi)啟了啟動(dòng)時(shí)檢測(cè),則直接拋出異常 */ // If the startup detection is opened, the Exception is thrown directly. boolean check = getUrl().getParameter(Constants.CHECK_KEY, true) && url.getParameter(Constants.CHECK_KEY, true) && !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol()); boolean skipFailback = t instanceof SkipFailbackWrapperException; if (check || skipFailback) { if (skipFailback) { t = t.getCause(); } throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t); } else { logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t); } /** 記錄失敗列表,定時(shí)重試 */ // Record a failed registration request to a failed list, retry regularly, failedRegistered.add(url); } }
第一行繼續(xù)調(diào)用父類(lèi)AbstractRegistry的構(gòu)造方法,不做復(fù)雜事,就緩存
image.png
完了之后繼續(xù)回到鉤子方法
image.png
具體就到具體的注冊(cè)中心了,比如ZookeeperRegistry,MulticastRegistry等。這里常用的是ZK,那就拿ZK舉例:@Override protected void doRegister(URL url) { try { /** 注冊(cè)即創(chuàng)建節(jié)點(diǎn) */ /** toUrlPath: /dubbo/dubbo.common.hello.service.HelloService/providers/dubbo%3A%2F%2F192.168.1.100%3A20880%2F dubbo.common.hello.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-provider%26 application.version%3D1.0%26dubbo%3D2.5.3%26environment%3Dproduct%26interface%3D dubbo.common.hello.service.HelloService%26methods%3DsayHello%26 organization%3Dchina%26owner%3Dcheng.xi%26pid%3D8920%26side%3Dprovider%26timestamp%3D1489828029449 默認(rèn)創(chuàng)建的節(jié)點(diǎn)是臨時(shí)節(jié)點(diǎn)*/ zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true)); } catch (Throwable e) { throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); } }
看最后一行,注冊(cè)就是創(chuàng)建節(jié)點(diǎn)
/** 創(chuàng)建節(jié)點(diǎn) */ /** * 在分布式系統(tǒng)中,我們常常需要知道某個(gè)機(jī)器是否可用,傳統(tǒng)的開(kāi)發(fā)中,可以通過(guò)Ping某個(gè)主機(jī)來(lái)實(shí)現(xiàn),Ping得通說(shuō)明對(duì)方是可用的,相反是不可用的; * ZK 中我們讓所有的機(jī)其都注冊(cè)一個(gè)臨時(shí)節(jié)點(diǎn),我們判斷一個(gè)機(jī)器是否可用,我們只需要判斷這個(gè)節(jié)點(diǎn)在ZK中是否存在就可以了,不需要直接去連接需要檢查的機(jī)器,降低系統(tǒng)的復(fù)雜度 * */ @Override public void create(String path, boolean ephemeral) { if (!ephemeral) { if (checkExists(path)) { return; } } int i = path.lastIndexOf('/'); if (i > 0) { create(path.substring(0, i), false); } if (ephemeral) { createEphemeral(path); } else { createPersistent(path); } }
zk節(jié)點(diǎn)路徑看上面代碼就很清晰了,以全路徑不斷往下創(chuàng)建,到最后具體時(shí)就創(chuàng)建臨時(shí)節(jié)點(diǎn)。
@Override public void createEphemeral(String path) { try { client.createEphemeral(path); } catch (ZkNodeExistsException e) { } }
catch里面的空邏輯解決的就是防止多個(gè)provider競(jìng)爭(zhēng)創(chuàng)建相同的父級(jí)持久節(jié)點(diǎn)。
這樣將自己注冊(cè)到注冊(cè)中心就完事了 -
3.4 監(jiān)聽(tīng)
image.png
provider在注冊(cè)到注冊(cè)中心之后,registry會(huì)去訂閱覆蓋配置的服務(wù),之后就會(huì)在/dubbo/xxx.xxx.service/XxxService節(jié)點(diǎn)下多一個(gè)configurators節(jié)點(diǎn)
這一步也是緩存注冊(cè)中心信息到本地的一個(gè)重要步驟,這就是為啥注冊(cè)中心掛了,provider與consumer還能通信的原理所在了。看代碼
image.png
進(jìn)入父類(lèi):FailbackRegistry
image.png
繼續(xù)到父類(lèi)緩存處理映射關(guān)系
image.png
回到FailbackRegistey中的鉤子方法
image.png
核心子類(lèi)處理,依然以ZK為例@Override protected void doSubscribe(final URL url, final NotifyListener listener) { try { if (Constants.ANY_VALUE.equals(url.getServiceInterface())) { String root = toRootPath(); ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url); if (listeners == null) { zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>()); listeners = zkListeners.get(url); } //將zkClient的事件IZkChildListener轉(zhuǎn)換到registry事件NotifyListener ChildListener zkListener = listeners.get(listener); if (zkListener == null) { listeners.putIfAbsent(listener, new ChildListener() { @Override public void childChanged(String parentPath, List<String> currentChilds) { for (String child : currentChilds) { child = URL.decode(child); if (!anyServices.contains(child)) { anyServices.add(child); subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child, Constants.CHECK_KEY, String.valueOf(false)), listener); } } } }); zkListener = listeners.get(listener); } zkClient.create(root, false); List<String> services = zkClient.addChildListener(root, zkListener); if (services != null && !services.isEmpty()) { for (String service : services) { service = URL.decode(service); anyServices.add(service); subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service, Constants.CHECK_KEY, String.valueOf(false)), listener); } } } else { List<URL> urls = new ArrayList<URL>(); // 這里的path分別為providers,routers,configurators三種 /** 分別對(duì)providers,routers,configurators三種不同類(lèi)型的進(jìn)行訂閱,也就是往zookeeper中注冊(cè)節(jié)點(diǎn),注冊(cè)之前先給url添加監(jiān)聽(tīng)器。最后是訂閱完之后進(jìn)行通知 */ for (String path : toCategoriesPath(url)) { ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url); if (listeners == null) { zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>()); listeners = zkListeners.get(url); } ChildListener zkListener = listeners.get(listener); if (zkListener == null) { listeners.putIfAbsent(listener, new ChildListener() { /** * 這里設(shè)置了監(jiān)聽(tīng)回調(diào)的地址,即回調(diào)給FailbackRegistry中的notify * 當(dāng)關(guān)注的路徑的下增減節(jié)點(diǎn),就會(huì)觸發(fā)回調(diào),然后通過(guò)notify方法,進(jìn)行業(yè)務(wù)數(shù)據(jù)的變更邏輯 */ @Override public void childChanged(String parentPath, List<String> currentChilds) { ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)); } }); zkListener = listeners.get(listener); } /** 創(chuàng)建持久節(jié)點(diǎn) */ //創(chuàng)建三個(gè)節(jié)點(diǎn) // /dubbo/.../providers/ // /dubbo/.../configurators/ // /dubbo/.../routers/ //上面三個(gè)路徑會(huì)被消費(fèi)者端監(jiān)聽(tīng),當(dāng)提供者,配置,路由發(fā)生變化之后, //注冊(cè)中心會(huì)通知消費(fèi)者刷新本地緩存。 zkClient.create(path, false); /** 開(kāi)始對(duì)該節(jié)點(diǎn)設(shè)置監(jiān)聽(tīng) */ List<String> children = zkClient.addChildListener(path, zkListener); if (children != null) { urls.addAll(toUrlsWithEmpty(url, path, children)); } } /** 下面要開(kāi)始更新新的服務(wù)信息,服務(wù)啟動(dòng)和節(jié)點(diǎn)更新回調(diào)(前面設(shè)置了回調(diào)到這里)都會(huì)調(diào)用到這里 */ notify(url, listener, urls); } } catch (Throwable e) { throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); } }
劃重點(diǎn)
image.png
這里初始化了ChildListener,并且當(dāng)關(guān)注的路徑的下增減節(jié)點(diǎn),就會(huì)觸發(fā)回調(diào),然后通過(guò)notify方法,進(jìn)行業(yè)務(wù)數(shù)據(jù)的變更邏輯,這里notify里的參數(shù)linstener就是下圖圈定的linstener
image.png
啟動(dòng)時(shí),觸發(fā)一下監(jiān)聽(tīng)事件,用以更新回調(diào)
image.png
經(jīng)過(guò)一系列父類(lèi)方法觸發(fā)之后,核心邏輯調(diào)用到AbstractRegistry
/** 開(kāi)始更新本地緩存文件的信息 */ protected void notify(URL url, NotifyListener listener, List<URL> urls) { if (url == null) { throw new IllegalArgumentException("notify url == null"); } if (listener == null) { throw new IllegalArgumentException("notify listener == null"); } if ((urls == null || urls.isEmpty()) && !Constants.ANY_VALUE.equals(url.getServiceInterface())) { logger.warn("Ignore empty notify urls for subscribe url " + url); return; } if (logger.isInfoEnabled()) { logger.info("Notify urls for subscribe url " + url + ", urls: " + urls); } Map<String, List<URL>> result = new HashMap<String, List<URL>>(); // 獲取catagory列表,providers,routers,configurators for (URL u : urls) { if (UrlUtils.isMatch(url, u)) { // 不同類(lèi)型的數(shù)據(jù)分開(kāi)通知,providers,consumers,routers,overrides // 允許只通知其中一種類(lèi)型,但該類(lèi)型的數(shù)據(jù)必須是全量的,不是增量的。 String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); List<URL> categoryList = result.get(category); if (categoryList == null) { categoryList = new ArrayList<URL>(); result.put(category, categoryList); } categoryList.add(u); } } if (result.size() == 0) { return; } // 已經(jīng)通知過(guò) Map<String, List<URL>> categoryNotified = notified.get(url); if (categoryNotified == null) { notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>()); categoryNotified = notified.get(url); } //對(duì)這里得到的providers,configurators,routers分別進(jìn)行通知 for (Map.Entry<String, List<URL>> entry : result.entrySet()) { String category = entry.getKey(); List<URL> categoryList = entry.getValue(); categoryNotified.put(category, categoryList); /** 更新本地緩存文件,用以保證procider 與 consumer 的通信 */ saveProperties(url); //上面獲取到的監(jiān)聽(tīng)器進(jìn)行通知 --> 到RegistryDirectory中查看notify方法 /** * 對(duì)于消費(fèi)者來(lái)說(shuō)這里listener是RegistryDirectory * 而對(duì)于服務(wù)提供者來(lái)說(shuō)這里是OverrideListener,是RegistryProtocol的內(nèi)部類(lèi) */ listener.notify(categoryList); } }
看圈定的重點(diǎn)
- 1.更新本地配置,解決了注冊(cè)中心掛了也能通信的問(wèn)題,不多做解釋。
- 2.觸發(fā)監(jiān)聽(tīng)器的監(jiān)聽(tīng)事件,這一步在消費(fèi)端做詳細(xì)解釋
- 整個(gè)服務(wù)暴露過(guò)程結(jié)束。