前言
歡迎關注本套Java系列文章,導航地址:Java架構師成長之路
關於Dubbo,本系列文章主要講三方面內容。前兩講我們已經瞭解到Dubbo的基本特性,常用配置以及簡單瞭解了一下Dubbo自適應擴展點源碼的內容。這一節,就讓我們深入走進Dubbo的源碼世界的核心,一探究竟吧~
- 解開Dubbo的神祕面紗
- Dubbo常用配置
- Dubbo源碼分析(上篇)
- Dubbo源碼分析(中篇)
- Dubbo源碼分析(下篇)
關於Dubbo 的源碼,我想從以下幾個方面來介紹:
- Dubbo Extension擴展點
- 服務端的服務發佈與註冊過程
- 消費端初始化過程
- 服務端調用過程
- Directory
- Cluster
- LoadBalance
在Dubbo源碼分析這一部分,由於篇幅過長,我這裏分上下兩節來解析這7個方面內容
那麼本節作爲dubbo源碼的第一篇,我們就來說一下服務發佈的過程吧~
(注:1. Dubbo Extension擴展點 在上一小節Dubbo常用配置已講,這裏做一個複習)
Tips:本節文末有Dubbo中文註釋版的源碼哦~
首先,我們從官方下載dubbo的源碼到本地,然後我們會發現它的項目結構如下(目前我使用的是3.5.4版本):
學習源碼,第一點就是了解整體架構,源碼結構,然後再找入口,逐層分析~
這裏引用一下網友的文章:dubbo源碼結構目錄分析
Dubbo Extension擴展點
Extension自適應擴展點是貫穿整個Dubbo 源碼的一個常見技術,所以很重要~ 在上一節,我們基於這段代碼:
//根據指定名稱獲取擴展點
Protocol myProtocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol");
//自適應擴展點
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class). getAdaptiveExtension();
作爲入口進行了簡單的分析,這裏我整理一下上節getAdaptiveExtension的流程圖,當做複習,這裏就不重複講解了,有興趣的可以看我上一篇的關於Dubbo Extension擴展點的介紹~
服務發佈流程
首先我們要明確的是,我們使用Dubbo這款RPC框架,繞不過最重要的兩個對象:
- 服務端 (發佈服務,向註冊中心註冊服務)
- 消費端 (訂閱服務,與服務端建立連接)
- 註冊中心(協調服務端與消費端的中間成員)
- 監視器 (RPC調用次數和調用時間監控)
那麼本節,我們主要講Provider是怎麼暴露服務,並向註冊中心註冊服務的?
首先我們思考:Dubbo的配置文件自定義的標籤在Dubbo源碼裏如何實現?然後是Dubbo如何解析自定義的配置文件以及調用實現客戶端與服務端之間的調用呢?
Spring對外留出的擴展
Dubbo是基於Spring 配置來實現服務的發佈的,那麼一定是基於Spring的擴展來寫了一套自己的標籤,那麼spring是如何解析這些配置呢?
總的來說,就是可以通過Spring的擴展機制來擴展自己的標籤。大家在Dubbo配置文件中看到的 <dubbo:registry>
、<dubbo:service>
等,就是基於Spring做的自定義擴展標籤
在dubbo-config模塊下的dubbo-config-spring模塊,就是主要來做Spring擴展標籤用的:
要實現自定義擴展,有三個步驟(在Spring中定義了兩個接口,用來實現擴展)
- DubboNamespaceHandler接口: 註冊一堆BeanDefinitionParser,利用他們來進行解析
- DubboBeanDefinitionParser接口:用於解析配置文件每個裏element的內容
- Spring默認會加載jar包下的META-INF/spring.handlers文件尋找對應的NamespaceHandler
Dubbo的接入實現
當我們使用Dubbo給出的xml標籤,Spring是如何完成解析的呢?
Dubbo中Spring擴展就是使用Spring的自定義類型,在dubbo-config-spring這一模塊有兩個類,分別對於Spring進行擴展:
- DubboNamespaceHandler類對於NamespaceHandlerSupport的擴展
這裏很好理解,就是我們在dubbo配置文件裏使用的自定義標籤,將所有對應的標籤內容轉化爲對應的Conifg配置類:
比如用戶定義了這樣的一個配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 提供方應用信息,用於計算依賴關係 -->
<dubbo:application name="hello-world-app"/>
<!-- 使用multicast廣播註冊中心暴露服務地址 -->
<dubbo:registry id="zookeeper" address="zookeeper://192.168.200.111:2181" file="d:/dubbo-server" />
<!-- 聲明需要暴露的服務接口 -->
<dubbo:reference id="demoService"
interface="com.test.dubbo.IGpHello"
registry="zookeeper"
version="1.0.1"/>
</beans>
這裏的每個標籤名在源碼裏對應一個配置類
標籤名 | 源碼配置類名稱 |
---|---|
dubbo:application | ApplicationConfig.class |
dubbo:registry | RegistryConfig.class |
… | xxx.class |
我們以<dubbo:application>
爲例,然後我們看對應application的配置類ApplicationConfig.class:
這裏我就舉一例足可說明問題,其他的字段參數可自行參考源碼類比。
所以DubboNamespaceHandler類要做的事情就是根據用戶配置的dubbo配置文件<dubbo:XXX>
,通過new DubboBeanDefinitionParser(parse裏面有一個方法處理element的,這段代碼很長,暫不貼出),遍歷element元素,將值賦予對應的Config類~
- DubboBeanDefinitionParser類對於BeanDefinitionParser的擴展
BeanDefinitionParser全部都使用了DubboBeanDefinitionParser,如果我們想看<dubbo:service>
等標籤的配置,就直接看DubboBeanDefinitionParser。這個裏面主要做了一件事,把不同的配置分別轉化成Spring容器中的bean對象
application對應ApplicationConfig
registry對應RegistryConfig
monitor對應MonitorConfig
provider對應ProviderConfig
consumer對應ConsumerConfig
…
爲了在Spring啓動的時候,也相應的啓動provider發佈服務註冊服務的過程,而同時爲了讓客戶端在啓動的時候自動訂閱發現服務,加入了兩個BeanServiceBean、ReferenceBean。分別繼承了ServiceConfig和ReferenceConfig同時還分別實現了InitializingBean、DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware
- InitializingBean 接口爲bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是繼承該接口的類,在初始化bean的時候會執行該方法。
- DisposableBean bean被銷燬的時候,spring容器會自動執行destory方法,比如釋放資源
- ApplicationContextAware 實現了這個接口的bean,當spring容器初始化的時候,會自動的將ApplicationContext注入進來
- ApplicationListener ApplicationEvent事件監聽,spring容器啓動後會發一個事件通知
- BeanNameAware 獲得自身初始化時,本身的bean的id屬性
那麼基本的實現思路可以整理出來了~
- 利用Spring的解析收集xml中的配置信息,然後把這些配置信息存儲到serviceConfig中
- 調用ServiceConfig的export方法來進行服務的發佈和註冊
服務發佈流程解析
上面簡單的梳理了一下dubbo-config-spring 這個模塊的整體模型,接下來我們根據源碼的思路,看看如何實現服務發佈?
ServiceBean繼承了InitializingBean,所以重載父類的afterPropertiesSet方法必然優先執行。
ServiceBean是服務發佈的切入點,通過afterPropertiesSet方法,調用export()方法進行發佈。
isDelay是做什麼用的?
如果dubbo配置文件配置了這樣的字段
<dubbo:provider delay="10" />
ServiceBean這裏會判斷一下
private boolean isDelay() {
Integer delay = getDelay();
ProviderConfig provider = getProvider();
if (delay == null && provider != null) {
delay = provider.getDelay();
}
return supportedApplicationListener && (delay == null || delay.intValue() == -1);
}
回過頭來看,export爲父類ServiceConfig中的方法,所以跳轉到SeviceConfig類中的export方法
public class ServiceBean<T> extends ServiceConfig<T> {}
delay的使用
我們看ServiceBean的父類SeviceConfig.export()
public synchronized void export() {
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && ! export.booleanValue()) {
return;
}
if (delay != null && delay > 0) {
Thread thread = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(delay);
} catch (Throwable e) {
}
doExport();
}
});
thread.setDaemon(true);
thread.setName("DelayExportServiceThread");
thread.start();
} else {
doExport();
}
}
Tips : delay由SeviceConfig的父類AbstractServiceConfig定義的Integer字段
我們發現,delay的作用就是延遲暴露,而延遲的方式也很直截了當 Thread.sleep(delay)
- export是synchronized修飾的方法。也就是說暴露的過程是原子操作,正常情況下不會出現鎖競爭的問題,畢竟初始化過程大多數情況下都是單一線程操作,這裏聯想到了Spring的初始化流程,也進行了加鎖操作,這裏也給我們平時設計一個不錯的啓示:初始化流程的性能調優優先級應該放的比較低,但是安全的優先級應該放的比較高!
- 繼續看doExport()方法。同樣是一堆初始化代碼
export的過程
繼續看doExport(),最終會調用到doExportUrls()中:
private void doExportUrls() {
//是不是獲得註冊中心的配置
List<URL> registryURLs = loadRegistries(true);
//是不是支持多協議發佈
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
啓動一個服務的時候,做了什麼事情?
調用註冊中心發佈服務到zookeeper,啓動一個netty
通過debug到protocols處(我之前在配置文件裏只配置了一個註冊中心的地址)<dubbo:protocol name="dubbo" port="20888" id="dubbo" />
所以protocols處只有一個地址
protocols也是根據配置裝配出來的。接下來讓我們進入doExportUrlsFor1Protocol方法看看dubbo具體是怎麼樣將服務暴露出去的
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// ...部分代碼省略,有興趣的同學可以參考文末中文版源碼
//如果scope沒有配置或者配置local、remote,dubbo會將服務export到本地,
// 意思就是:將服務以injvm協議export出去,如果是同一個jvm的應用可以直接通過jvm發起調用,
// 而不需要通過網絡發起遠程調用。
if ("injvm".equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
// 導出服務
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
String scope = url.getParameter(Constants.SCOPE_KEY);
//配置爲none不暴露
if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
//配置不是remote的情況下做本地暴露 (配置爲remote,則表示只暴露遠程服務)
//發佈服務
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
//如果配置不是local則暴露爲遠程服務.(配置爲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.size() > 0
&& url.getParameter("register", true)) {
for (URL registryURL : registryURLs) {//
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
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);
}
//通過proxyFactory來獲取Invoker對象
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//註冊服務
Exporter<?> exporter = protocol.export(invoker);
//將exporter添加到list中
exporters.add(exporter);
}
} else {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
看到這裏就比較明白dubbo的工作原理了doExportUrlsFor1Protocol方法,先創建兩個URL,分別如下
dubbo://192.168.xx.63:20888/com.test.IGHello;
registry://192.168:20888/com.test.IGHello ;
是不是覺得這個URL很眼熟,沒錯!在註冊中心看到的services的providers信息就是這個在上面這段代碼中可以看到Dubbo的比較核心的抽象:Invoker, Invoker是一個代理類,從ProxyFactory中生成。
Tips:
- Invoker - 執行具體的遠程調用(這塊後續單獨講)
- Protocol – 服務地址的發佈和訂閱
- Exporter – 暴露服務或取消暴露
註冊服務
前面講了dubbo解析配置文件,賦值到對應的配置類,最終走到核心流程,將攜帶的URL進行註冊服務:
//註冊服務-核心流程
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
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);
}
//step 1 :通過proxyFactory來獲取Invoker對象
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//step 2 :註冊服務
Exporter<?> exporter = protocol.export(invoker);
//step 3 :將exporter添加到list中
exporters.add(exporter);
}
ok,源碼到這裏開始分劇情介紹了,這裏主要是兩個步驟:
- 通過proxyFactory來獲取Invoker對象
- export 註冊服務
我們先說export註冊服務部分:
protocol來自ServiceConfig私有成員變量:
private static final Protocol protocol = ExtensionLoader.
getExtensionLoader(Protocol.class).
getAdaptiveExtension(); //Protocol$Adaptive
我們之前提到過,protocol 明顯是一個自適應擴展點~
實際上這個Protocol得到的應該是一個Protocol$Adaptive
。一個自適應的適配器。這個時候,通過protocol.export(invoker),實際上調用的應該是Protocol$Adaptive這個動態類的export方法。我們再看看這段代碼(這段代碼,在上一節講dubbo自適應擴展點時,debug時我曾貼出此段代碼):
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
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!");
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
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.RpcException {
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.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])");
Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
return extension.export(arg0);
}
}
上面這段代碼做兩個事情
- 從url中獲得protocol的協議地址,如果protocol爲空,表示已dubbo協議發佈服務,否則根據配置的協議類型來發布服務。
爲什麼叫基於適配器的擴展點?原因就在:如果我們請求的URL協議是Hession/RMI/…那麼就要改代碼
類似於if(“RMI”== URL),else if(“Hession”== URL)…,每個請求的協議都要加一個else if 判斷,所以這裏通過適配器,封裝成統一的register://XXXd地址,自適應轉換URL達成效果~ - 調用ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName)
這個方法的主要作用是用來獲取ExtensionLoader實例代表的擴展的指定實現。已擴展實現的名字作爲參數,結合前面學習getExtension的代碼.:
@SuppressWarnings("unchecked")
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
//判斷是否已經緩存過該擴展點
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//createExtension ,創建擴展點
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
通過debug看一下,此時invoke的攜帶的URL。
注:在上一小節,我們演示了dubbo的客戶端與服務端的demo,這裏我啓動的的是dubbo的服務端的main函數debug模式:
public static void main( String[] args )
{
Main.main(new String[]{"spring","log4j"});
}
這個地址就是我們之前在zk節點上獲取到拼接地址!
我們再回來看指定名稱的自適應擴展點,那麼這裏返回的extension實際上是什麼呢?
那就要看這裏是什麼類型的自適應擴展點了?這裏明顯是指定名稱類型的, 當extName爲registry的時候,我們不需要再次去閱讀這塊代碼了,直接可以在擴展點中找到相應的實現擴展點[/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol] 配置如下:
因此,此時的extension就是com.alibaba.dubbo.registry.integration.RegistryProtocol,那麼意味着,Protocol$Adaptive在最後返回的extension.export(arg0)就是傳入的RegistryProtocol,則在step 2:註冊服務後,進入RegistryProtocol方法的export:
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//registry provider
final Registry registry = getRegistry(originInvoker);
//得到需要註冊到zk上的協議地址,也就是dubbo://
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
registry.register(registedProviderUrl);
// 訂閱override數據
// FIXME 提供者訂閱時,會影響同一JVM即暴露服務,又引用同一服務的的場景,因爲subscribed以服務名爲緩存的key,導致訂閱信息覆蓋。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保證每次export都返回一個新的exporter實例
return new Exporter<T>() {
public Invoker<T> getInvoker() {
return exporter.getInvoker();
}
public void unexport() {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
registry.unregister(registedProviderUrl);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
overrideListeners.remove(overrideSubscribeUrl);
registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
};
}
上面又調用了幾個方法,接下來我們分析doLocalExport,之後再回到主線劇情,分別來看:
doLocalExport
本地先啓動監聽服務
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) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return (ExporterChangeableWrapper<T>) exporter;
}
上面代碼中,protocol代碼是怎麼賦值的呢?我們看看代碼,熟悉嗎?是一個依賴注入的擴展點。不熟悉的話,我們再回想一下,在加載擴展點的時候,有一個injectExtension方法,針對已經加載的擴展點中的擴展點屬性進行依賴注入。
private Protocol protocol;
public void setProtocol(Protocol protocol) {
this.protocol = protocol;
}
protocol.export
因此我們知道protocol是一個自適應擴展點,ProtocolAdaptive.export方法中,ExtensionLoader.getExtension(Protocol.class).getExtension。應該就是基於DubboProtocol協議去發佈服務了嗎?如果是這樣,那你們太單純了。
這裏並不是獲得一個單純的DubboProtocol擴展點,而是會通過Wrapper對Protocol進行裝飾,裝飾器分別爲: ProtocolFilterWrapper/ ProtocolListenerWrapper; 至於MockProtocol爲什麼不在裝飾器裏面呢?大家再回想一下我們在看ExtensionLoader.loadFile這段代碼的時候,有一個判斷,裝飾器必須要具備一個帶有Protocol的構造方法,如下:
public ProtocolFilterWrapper(Protocol protocol){
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
截止到這裏,我們已經知道,Protocol$Adaptive裏面的export方法,會調用ProtocolFilterWrapper以及ProtocolListenerWrapper類的方法
這兩個裝飾器是用來幹嘛的呢?我們來分析下
ProtocolFilterWrapper和ProtocolListenerWrapper
- ProtocolFilterWrapper
這個類非常重要,dubbo機制裏面日誌記錄、超時等等功能都是在這一部分實現的
這個類有3個特點:
- 它有一個參數爲Protocol protocol的構造函數
- 它實現了Protocol接口
- 它使用責任鏈模式,對export和refer函數進行了封裝,部分代碼如下
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
public void destroy() {
protocol.destroy();
}
//buildInvokerChain函數:它讀取所有的filter類,利用這些類封裝invoker
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);//自動激活擴展點,根據條件獲取當前擴展可自動激活的實現
if (filters.size() > 0) {
for (int i = filters.size() - 1; i >= 0; i --) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
public Class<T> getInterface() {
return invoker.getInterface();
}
public URL getUrl() {
return invoker.getUrl();
}
public boolean isAvailable() {
return invoker.isAvailable();
}
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
我們看如下文件: /dubbo-rpc-api/src/main/resources/METAINF/dubbo/internal/com.alibaba.dubbo.rpc.Filter
其實就是對Invoker,通過如下的Filter組裝成一個責任鏈
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
token=com.alibaba.dubbo.rpc.filter.TokenFilter
accesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilter
activelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
classloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFilter
context=com.alibaba.dubbo.rpc.filter.ContextFilter
consumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
executelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFilter
compatible=com.alibaba.dubbo.rpc.filter.CompatibleFilter
timeout=com.alibaba.dubbo.rpc.filter.TimeoutFilter
這其中涉及到很多功能,包括權限驗證、異常、超時等等,當然可以預計計算調用時間等等應該也是在這其中的某個類實現的;這裏我們可以看到export和refer過程都會被filter過濾
- ProtocolListenerWrapper
在這裏我們可以看到export和refer分別對應了不同的Wrapper;export是對應的ListenerExporterWrapper。這塊暫時先不去分析,因爲這個地方並沒有提供實現類。
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return new ListenerExporterWrapper<T>(protocol.export(invoker),
Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
.getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
Collections.unmodifiableList(
ExtensionLoader.getExtensionLoader(InvokerListener.class)
.getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));
}
DubboProtocol.export
通過上面的代碼分析完以後,最終我們能夠定位到DubboProtocol.export方法。我們看一下dubboProtocol的export方法:openServer(url)
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//export an stub service for dispaching event
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);
}
}
//暴露服務
openServer(url);
return exporter;
}
openServer
private void openServer(URL url) {
// find server.
String key = url.getAddress();//192.168.11.156:20880
//client 也可以暴露一個只有server可以調用的服務。
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY,true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {//沒有的話就是創建服務
serverMap.put(key, createServer(url));
} else {
//server支持reset,配合override功能使用
server.reset(url);
}
}
}
createServer
創建服務,開啓心跳檢測,默認使用netty。組裝url
private ExchangeServer createServer(URL url) {
//默認開啓server關閉時發送readonly事件
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
//默認開啓heartbeat
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, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
ExchangeServer server;
try {
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;
}
Exchangers.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");
return getExchanger(url).bind(url, handler);
}
getExchanger
通過ExtensionLoader獲得指定的擴展點,type默認爲header
public static Exchanger getExchanger(URL url) {
//url中獲得exchanger, 默認爲header
String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
return getExchanger(type);
}
public static Exchanger getExchanger(String type) {
return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
}
HeaderExchanger.bind
調用headerExchanger的bind方法
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
Transporters.bind
通過transporter.bind來進行綁定。
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
return getTransporter().bind(url, handler);
}
NettyTransport.bind
通過NettyTranport創建基於Netty的server服務
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
new HeaderExchangeServer
在調用HeaderExchanger.bind方法的時候,是先new一個HeaderExchangeServer. 這個server是幹嘛呢? 是對當前這個連接去建立心跳機制
public class HeaderExchangeServer implements ExchangeServer {
private final ScheduledExecutorService scheduled = Executors.
newScheduledThreadPool(1,new NamedThreadFactory(
"dubbo-remoting-server-heartbeat", true));
// 心跳定時器
private ScheduledFuture<?> heatbeatTimer;
// 心跳超時,毫秒。缺省0,不會執行心跳。
private int heartbeat;
private int heartbeatTimeout;
private final Server server;
private volatile boolean closed = false;
public HeaderExchangeServer(Server server) {
//..屬性賦值
//心跳
startHeatbeatTimer();
}
private void startHeatbeatTimer() {
//關閉心跳定時
stopHeartbeatTimer();
if (heartbeat > 0) {
//每隔heartbeat時間執行一次
heatbeatTimer = scheduled.scheduleWithFixedDelay(
new HeartBeatTask( new HeartBeatTask.ChannelProvider() {
//獲取channels
public Collection<Channel> getChannels() {
return Collections.unmodifiableCollection(
HeaderExchangeServer.this.getChannels() );
}
}, heartbeat, heartbeatTimeout),
heartbeat, heartbeat,TimeUnit.MILLISECONDS);
}
}
//關閉心跳定時
private void stopHeartbeatTimer() {
try {
ScheduledFuture<?> timer = heatbeatTimer;
if (timer != null && ! timer.isCancelled()) {
timer.cancel(true);
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
} finally {
heatbeatTimer =null;
}
}
心跳線程HeartBeatTask
在超時時間之內,發送數據
在超時時間在外,是客戶端的話,重連;是服務端,那麼關閉
服務發佈總結
直接從官方網站上扒了一個圖過來,好這個圖顯示的很清楚了。
通過proxyFactory來獲取Invoker對象
前面在ServiceConfig主線上劇情:
- 通過proxyFactory來獲取Invoker對象
- 註冊服務
接下來,我們說看一下通過proxyFactory來獲取Invoker對象這一過程
//註冊服務-核心流程
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
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);
}
//step 1 :通過proxyFactory來獲取Invoker對象
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//step 2 :註冊服務
Exporter<?> exporter = protocol.export(invoker);
//step 3 :將exporter添加到list中
exporters.add(exporter);
}
proxyFactory是什麼呢?
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
明顯是一個擴展點,那此時的proxyFactory實際上是?
定義在方法上的@Adaptice,所以應該是ProxyFactory$Adaptice
通過ExtensionLoader.createAdaptiveExtensionClass,我們debug一下,拿到了這個
String extName = url.getParameter(“proxy”, “javassist”);
這裏的自適應擴展點是根據proxy獲取的,如果沒有默認是javassist,所以,我們看看配置文件對應的配置
javassist=com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory
所以看com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory
也就是說:
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
這一步,獲取Invoker是在JavassistProxyFactory實現的!同樣,如果是debug也是證實我們的猜想:
接下來,看看Wrapper.getWrapper裏面做了什麼事情?
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper類不能正確處理帶$的類名
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
這裏我們斷點看一下,動態生成的Wrapper:
我這裏拿出c3:
取到服務端的服務了~
接下來回到主線:
也證實了我們之前的猜想,此時通過動態生成的invoke的對象是JavassistProxyFactory,然後,此時主線劇情就是:
拼裝好URL,並且通過代理拿到服務端的實例,接下來export到網絡層傳輸服務~
這一小段總結:
服務註冊流程
前面,我們已經知道,基於Spring這個解析入口,到發佈服務的過程,接着基於DubboProtocol去發佈,最終調用Netty的api創建了一個NettyServer。
那麼繼續沿着RegistryProtocol.export這個方法,來看看註冊服務的代碼
RegistryProtocol.export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker); //發佈本地服務
//registry provider
final Registry registry = getRegistry(originInvoker);
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
registry.register(registedProviderUrl);
// 訂閱override數據
// FIXME 提供者訂閱時,會影響同一JVM即暴露服務,又引用同一服務的的場景,因爲subscribed以服務名爲緩存的key,導致訂閱信息覆蓋。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保證每次export都返回一個新的exporter實例
return new Exporter<T>() {
public Invoker<T> getInvoker() {
return exporter.getInvoker();
}
public void unexport() {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
registry.unregister(registedProviderUrl);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
overrideListeners.remove(overrideSubscribeUrl);
registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
};
}
Tips:前面我提到的doLocalExport這個方法到這裏,算是將服務發佈的流程講完,現在我們回到主線,看看另外一個重要的步驟:服務註冊getRegistry~
getRegistry
這個方法是invoker的地址獲取registry實例
/**
* 根據invoker的地址獲取registry實例
* @param originInvoker
* @return
*/
private Registry getRegistry(final Invoker<?> originInvoker){
URL registryUrl = originInvoker.getUrl(); //獲得registry://192.168.200.111:2181的協議地址
if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {
//得到zookeeper的協議地址
String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY);
//registryUrl就會變成了zookeeper://192.168.200.111
registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY);
}
//registryFactory是什麼?
return registryFactory.getRegistry(registryUrl);
}
registryFactory.getRegistry
這段代碼很明顯了,通過前面這段代碼的分析,其實就是把registry的協議頭改成服務提供者配置的協議地址,也就是我們配置的
<dubbo:registry address=”zookeeper://192.168.200.111:2181”/>
然後registryFactory.getRegistry的目的,就是通過協議地址匹配到對應的註冊中心。那registryFactory是一個什麼樣的對象呢?,我們找一下這個代碼的定義
private RegistryFactory registryFactory;
public void setRegistryFactory(RegistryFactory registryFactory) {
this.registryFactory = registryFactory;
}
這個代碼有點眼熟,再來看看RegistryFactory這個類的定義,我猜想一定是一個擴展點,不信,咱們看
並且,大家還要注意這裏面的一個方法上,有一個@Adaptive的註解,說明什麼? 這個是一個自適應擴展點。按照我們之前看過代碼,自適應擴展點加在方法層面上,表示會動態生成一個自適應的適配器。所以這個自適應適配器應該是RegistryFactory$Adaptive
@SPI("dubbo")
public interface RegistryFactory {
/**
* 連接註冊中心.
*
* 連接註冊中心需處理契約:<br>
* 1. 當設置check=false時表示不檢查連接,否則在連接不上時拋出異常。<br>
* 2. 支持URL上的username:password權限認證。<br>
* 3. 支持backup=10.20.153.10備選註冊中心集羣地址。<br>
* 4. 支持file=registry.cache本地磁盤文件緩存。<br>
* 5. 支持timeout=1000請求超時設置。<br>
* 6. 支持session=60000會話超時或過期設置。<br>
*
* @param url 註冊中心地址,不允許爲空
* @return 註冊中心引用,總不返回空
*/
@Adaptive({"protocol"})
Registry getRegistry(URL url);
}
RegistryFactory$Adaptive
我們拿到這個動態生成的自適應擴展點,看看這段代碼裏面的實現
- 從url中拿到協議頭信息,這個時候的協議頭是zookeeper://
- 通過ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(“zookeeper”)去獲得一個指定的擴展點,而這個擴展點的配置在
dubbo-registry-zookeeper/resources/META-INF/dubbo/internal/com.alibaba.dubbo.registry.RegistryFactory。得到一個ZookeeperRegistryFactory
public class RegistryFactory$Adaptive implements com.alibaba.dubbo.registry.RegistryFactory {
public com.alibaba.dubbo.registry.Registry getRegistry(com.alibaba.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.registry.RegistryFactory) " +
"name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.registry.RegistryFactory extension =
(com.alibaba.dubbo.registry.RegistryFactory)
ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.registry.RegistryFactory.class).
getExtension(extName);
return extension.getRegistry(arg0);
}
}
ZookeeperRegistryFactory
這個方法中並沒有getRegistry方法,而是在父類AbstractRegistryFactory
- 從緩存REGISTRIES中,根據key獲得對應的Registry
- 如果不存在,則創建Registry
public Registry getRegistry(URL url) {
url = url.setPath(RegistryService.class.getName())
.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
String key = url.toServiceString();
// 鎖定註冊中心獲取過程,保證註冊中心單一實例
LOCK.lock();
try {
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
}
registry = createRegistry(url);
if (registry == null) {
throw new IllegalStateException("Can not create registry " + url);
}
REGISTRIES.put(key, registry);
return registry;
} finally {
// 釋放鎖
LOCK.unlock();
}
}
createRegistry
創建一個註冊中心,這個是一個抽象方法,具體的實現在對應的子類實例中實現的,在ZookeeperRegistryFactory中
public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}
通過zkClient,獲得一個zookeeper的連接實例
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
if (! group.startsWith(Constants.PATH_SEPARATOR)) {
group = Constants.PATH_SEPARATOR + group;
}
this.root = group; //設置根節點
zkClient = zookeeperTransporter.connect(url);//建立連接
zkClient.addStateListener(new StateListener() {
public void stateChanged(int state) {
if (state == RECONNECTED) {
try {
recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
});
}
代碼分析到這裏,我們對於getRegistry得出了一個結論,根據當前註冊中心的配置信息,獲得一個匹配的註冊中心,也就是ZookeeperRegistry
registry.register(registedProviderUrl);
好的,拿到了ZookeeperRegistry我們回到export主線,往下分析:
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// step 1 : export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//step 2 : registry provider
final Registry registry = getRegistry(originInvoker);
//得到需要註冊到zk上的協議地址,也就是dubbo://
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
registry.register(registedProviderUrl);
// 訂閱override數據
// FIXME 提供者訂閱時,會影響同一JVM即暴露服務,又引用同一服務的的場景,因爲subscribed以服務名爲緩存的key,導致訂閱信息覆蓋。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保證每次export都返回一個新的exporter實例
return new Exporter<T>() {
public Invoker<T> getInvoker() {
return exporter.getInvoker();
}
public void unexport() {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
registry.unregister(registedProviderUrl);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
overrideListeners.remove(overrideSubscribeUrl);
registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
};
}
會調用registry.register去講dubbo://的協議地址註冊到zookeeper上.
這個方法會調用FailbackRegistry類中的register. 爲什麼呢?因爲ZookeeperRegistry這個類中並沒有register這個方法,但是他的父類FailbackRegistry中存在register方法,而這個類又重寫了AbstractRegistry類中的register方法。所以我們可以直接定位FailbackRegistry這個類中的register方法中
FailbackRegistry.register
- FailbackRegistry,從名字上來看,是一個失敗重試機制
- 調用父類的register方法,講當前url添加到緩存集合中
- 調用doRegister方法,這個方法很明顯,是一個抽象方法,會由ZookeeperRegistry子類實現。
@Override
public void register(URL url) {
super.register(url);
failedRegistered.remove(url);
failedUnregistered.remove(url);
try {
// Core!! -> 向服務器端發送註冊請求
doRegister(url);
} catch (Exception e) {
Throwable t = e;
// 如果開啓了啓動時檢測,則直接拋出異常
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);
}
// 將失敗的註冊請求記錄到失敗列表,定時重試
failedRegistered.add(url);
}
}
ZookeeperRegistry.doRegister
這裏纔是真正的向服務器端發送註冊請求 doRegister(url);
終於找到你了,調用zkclient.create在zookeeper中創建一個節點。
protected void doRegister(URL url) {
try {
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);
}
}
後記
歡迎關注本套Java系列文章-地址導航:Java架構師成長之路
Dubbo中文註釋版:DubboV2.5.4下載地址