2-dubbo源碼分析之服務(wù)暴露

  • 先看官網(wǎng)兩張圖【引用來(lái)自官網(wǎng)】:


    image.png
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;
}
```
image.png

直接進(jìn)來(lái)就是這樣的效果,很顯然對(duì)于在配置中指定了registry屬性的,會(huì)在加載spring BeanDefinition的時(shí)候就加載了注冊(cè)中心。
返回的注冊(cè)中心URL是這樣的:


image.png

后面重點(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);
 }
image.png

image.png

image.png

image.png

image.png
最后進(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é)束。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,560評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,035評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,814評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,224評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,444評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,804評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,998評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評(píng)論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,237評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,665評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,927評(píng)論 1 287
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,706評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,993評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容