dubbo的服務的註冊與發現,須要經過第三方註冊中心來協助完成,目前dubbo支持的註冊中心包括 zookeeper,consul,etcd3,eureka,nacas,redis,sofa。這些註冊中心的不一樣支持在以後的篇章進行分享。html
在鋪墊一些基礎內容以前,根據若是下幾個問題來進行回答,或許能更好的闡明dubbo的實現服務的註冊和發現的實現過程。
一、dubbo是在什麼時機與註冊中心創建鏈接。
二、dubbo服務註冊和導出的時機在何時。
三、dubbo服務的訂閱時機是在何時。
四、dubbo服務的上下線是如何通知訂閱者的。
五、dubbo是如何把這些各類第三方註冊中心進行整合的。
爲了回答上面的五個問題,咱們一塊兒去從dubbo的源碼探尋答案,這些問題和服務的註冊有關,那麼首先咱們須要的就是去dubbo-registry這個源碼模塊去查詢。java
@SPI("dubbo") public interface RegistryFactory { @Adaptive({"protocol"}) Registry getRegistry(URL url); }
RegistryFactory 就是產生一個註冊中心的工程,它有個自適應的方法getRegistry,那麼咱們知道dubbo會經過javassist動態產生一個RegistryFactory$Adaptive類,而且getRegistry方法的內部實現大體是以下:redis
public class RegistryFactory$Adaptive implements RegistryFactory { @Override public Registry getRegistry(URL url) { if (url == null) throw new IllegalArgumentException("url == null"); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.registry.RegistryFactory) " + "name from url (" + url.toString() + ") use keys([protocol])"); RegistryFactory extension = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(extName); return extension.getRegistry(url); } }
它經過傳入的URL的protocol協議字段排判斷是什麼類型註冊中心。例如,url的protocol的協議是zookeeper,那麼就會根據SPI的ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension("zookeeper")獲得一個產生ZooKeeper註冊中心的工廠,也就是ZookeeperRegistryFactory,而ZookeeperRegistryFactory這個類的getRegistry就是返回一個Zookeeper註冊中心。spring
能夠看出其語義,一個註冊中心Registry是一個節點(extends Node),而且它具備註冊服務(extends RegistryService)的功能。
dubbo支持以下這些註冊中心zookeeper,consul,etcd3,eureka,nacas,redis,sofa,那麼就會產生相應以下的Registry:
ZookeeperRegistry,ConsulRegistry,EtcdRegistry,NacosRegistry,RedisRegistry,SofaRegistry。類圖以下:apache
因此咱們知道,這些註冊中心都是繼承FailbackRegistry,這個FailbackRegistry其意思就是說,若是一個服務註冊到當前某個註冊中心註冊失敗後,可會在後臺產生一個daemon線程,定時的把註冊失敗服務從新註冊,而且有必定的重試限制。
在上面的類圖中咱們並無發現有個名爲EurekaRegistry這樣的類,由於實現了另外一個接口ServiceDiscovery方式,類名爲EurekaServiceDiscovery來進行服務發現。這些不一樣的註冊中心的實現方式,會在下一個章節去討論它。bootstrap
dubbo的協議是經過名爲org.apache.dubbo.rpc.Protocol來進行抽象的,那麼註冊協議也是同樣的,是經過org.apache.dubbo.registry.integration.RegistryProtocol來表達的,繼承org.apache.dubbo.rpc.Protocol。RegistryPrtocol是擴展點Protocol的具體實現,在Dubbo系列之 (一)SPI擴展文章中提到,會一次調用其setter方法來注入其須要的屬性,RegistryPrtocol其中有個屬性就是RegistryFactory,那麼就要爲它注入一個具體的RegistryFactory,那麼這個具體的RegistryFactory工廠是什麼類型,答案就是上面的RegistryFactory$Adaptive。爲何?由於在Dubbo系列之 (一)SPI擴展中提到了注入的屬性對象會從SpringExtensionFactory和SpiExtensionFactory工廠中查詢,恰好RegistryFactory也是一個擴展點,因此會在SpiExtensionFactory找出,而且SpiExtensionFactory工廠的實現以下:後端
public class SpiExtensionFactory implements ExtensionFactory { @Override public <T> T getExtension(Class<T> type, String name) { if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type); if (!loader.getSupportedExtensions().isEmpty()) { return loader.getAdaptiveExtension(); } } return null; } }
因此知道是返回一個自適應的擴展點,即RegistryFactory$Adaptive。
Protocol協議具備導出服務export()功能,和引用服務refer()功能。在RegistryProtocol中,在這個2個方法內就有對服務註冊到註冊中心的操做。數據結構
在服務導出中,首先要有一個認知,這個認知會在後續章節中進行詳細的介紹,先開始知道有這麼一件事情便可,咱們作dubbo服務暴露的時候,咱們有2中方式,一種是經過註解的方式:
@DubboService,@Service(非spring的)。或者經過xml的方式<dubbo:service />。
無論採用哪種方式,最終須要暴露的服務首先會包裝成一個ServiceBean的對象。這個ServiceBean 持有具體須要服務註冊的對象ref。ServiceBean的類圖以下:
app
服務導出也是是一個繁瑣的過程,因此在後面的章節進行詳細的探討,本章咱們只要知道其服務導出引入與註冊中心交互。框架
一、若是是經過註解@DubboService,就是經過ServiceClassPostProcessor類,該類是實現了Spring的BeanDefinitionRegistryPostProcessor。因此經過registerBeans進行註冊。在@EnableDubbo註解上有一個@DubboComponentScan註解,該註解上的@export註解就會導入DubboComponentScanRegistrar類,在該類中完成DubboBootstrapApplicationListener的註冊。
二、若是是經過<dubbo:service />的方式,咱們知道Spring對於自定義的標籤,須要自已提供一個NamespaceHanlder的實現類來協助解析自定義標籤。而dubbo的NamespaceHanlder實現類爲DubboNamespaceHandler。DubboNamespaceHandler該類就有該監聽器的注入。
而且classpath下的META-INF下新增spring.hanlders和spring.schemes。內容以下:
spring.shemes :
http://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
說明dubbo的命名空間文件位置
spring.handler:
http://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
說明處理該命名空間下的自定義標籤經過DubboNamespaceHandler.
三、具體的Spring 自定義標籤運用能夠參考Netty自娛自樂之類Dubbo RPC 框架設計構想 【上篇】。
在通過以上的基礎鋪墊以後,咱們對Registry和RegistryProtocol協議進行測試。
本章主要主要的關注點在註冊上,把目光移到RegistryProtocol的registry方法上。
private void register(URL registryUrl, URL registeredProviderUrl) { Registry registry = registryFactory.getRegistry(registryUrl); registry.register(registeredProviderUrl); }
其中,
registryUrl 爲註冊的URL,例如:zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&export=dubbo://192.168.0.105:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-annotation-provider&bind.ip=192.168.0.105&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=9990&release=&side=provider×tamp=1596943034484&pid=9990®istry_protocol=zookeeper×tamp=1596943034477
registeredProviderUrl 爲服務提供者須要被註冊的URL。例如:dubbo://192.168.0.105:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-annotation-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=9990&release=&side=provider×tamp=1596943034484
從上面的樣例能夠知道,registeredProviderUrl就是registryUrl 中參數export中的值。
@Test public void testRegistry(){ // 根據SPI 獲取RegistryFactory 自適應註冊工廠 RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension(); //經過url.getProtocol 和registryFactory獲得 zookeeper註冊中心 URL registryUrl=URL.valueOf("zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&export=dubbo://192.168.0.105:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-annotation-provider&bind.ip=192.168.0.105&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=9990&release=&side=provider×tamp=1596943034484&pid=9990®istry_protocol=zookeeper×tamp=1596943034477"); Registry zookeeperRegistry = registryFactory.getRegistry(registryUrl); //根據zookeeperRegistry註冊中心註冊,須要的服務providerRegistryURL URL providerRegistryURL=URL.valueOf("dubbo://192.168.0.105:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-annotation-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=9990&release=&side=provider×tamp=1596943034484"); zookeeperRegistry.register(providerRegistryURL); }
registry方法定位到FailbackRegistry,主要做用當服務註冊失敗後,能夠在後端線程重試。
public void register(URL url) { // 判斷該註冊中心能接受的協議 if (!acceptable(url)) { logger.info("URL " + url + " will not be registered to Registry. Registry " + url + " does not accept service of this protocol type."); return; } // 調用AbstractRegistry的register(),主要是吧註冊的URL放入registered集合中,說明該URL已經要被註冊 super.register(url); removeFailedRegistered(url); // 當前URL須要被註冊,因此把它從註冊失敗列表裏移除,由於多是重試註冊。 removeFailedUnregistered(url); // 當前URL須要被註冊,因此把它從註銷失敗列表裏移除,由於多是重試註冊。 try { //調用子類的具體doRegister,模板方法 doRegister(url); } catch (Exception e) { Throwable t = e; // If the startup detection is opened, the Exception is thrown directly. // 查看是否check字段是否設置爲true. boolean check = getUrl().getParameter(Constants.CHECK_KEY, true) && url.getParameter(Constants.CHECK_KEY, true) && !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); } // 不然把註冊失敗的URL 添加到failedRegistered,註冊失敗列表 addFailedRegistered(url); } }
private void addFailedRegistered(URL url) { //獲取該註冊URL是否已經存在在註冊失敗列表裏,存在直接返回 FailedRegisteredTask oldOne = failedRegistered.get(url); if (oldOne != null) { return; } // 不然建立一個失敗註冊重試任務FailedRegisteredTask,放入failedRegistered中。 FailedRegisteredTask newTask = new FailedRegisteredTask(url, this); oldOne = failedRegistered.putIfAbsent(url, newTask); if (oldOne == null) { // 而後把該失敗註冊任務放入daemon線程retryTimer,定式從新註冊 retryTimer.newTimeout(newTask, retryPeriod, TimeUnit.MILLISECONDS); } }
因爲章節篇幅限時,具體的doRegistry方法在後面章節分享。在下一個章節詳細分析AbstractRegistry 的做用和FailbackRegistry的重試機制,而且詳細剖析ZookeeperRegistry。