1. 程式人生 > >Dubbo——服務暴露過程分析

Dubbo——服務暴露過程分析

    這篇文章來敘述對dubbo的服務暴露前的準備工作:

使用Spring配置檔案,通過main方法來啟動spring容器,來觀察dubbo服務的啟動過程。

dubbo配置檔案

    <context:component-scan base-package="com.wk.order.service.impl,com.wk.order.facade.impl"/>

    <dubbo:application name="dubbo-order-service"/>
    <!--<dubbo:registry address="multicast://224.5.6.7:1234" />-->
    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" />

    <dubbo:protocol name="dubbo" port="20880"/>
    <dubbo:service interface="com.wk.common.facade.OrderFacade" ref="orderFacadeImpl" loadbalance="roundrobin"/>

在說明暴露服務的流程之前,先明確知道幾個物件,在dubbo裡,預設使用JavassisFactory動態代理工廠來動態的產生擴充套件物件,然後根據擴充套件物件的export方法,根據url的引數配置,例如上述為dubbo協議,註冊中心為zookeeper。這樣呢就會根據url的配置使用DubboProtocol來進行服務的暴露,建立NettyServer。預設為netty啟動。預設也是Dubbo。會使用ZookeeperRegistry把服務註冊的註冊中心。

在Spring容器啟動時,會對Bean進行載入,Dubbo通過Spring自定義標籤擴充套件了自己的標籤。把屬於Dubbo的實體Bean註冊到Spring裡。


    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
    }

上面註冊了ServiceBean這個類,那麼ServiceBean是什麼什麼東西呢,觀察ServiceBean物件,它實現了InitializingBean介面,在該介面中有一個afterPropertiesSet方法,在Bean的屬性初始化時,Spring會預設呼叫該方法。該方法裡呼叫了關於dubbo的開始 export方法。那麼dubbo的服務暴露export方法就在這裡開始起航了。

第一步:在afterPropertiesSet裡會從applicationContext裡獲取諸多例項,諸多例項就是上述註冊器裡的Bean.class。經過這一層之後,就會呼叫export方法。此時在ServiceConfig物件裡,已經有了關於dubbo配置的一些內容。

走入export方法後共有多個方法一連串的呼叫:

doExport方法對一些配置進行檢測和獲取,檢查provider,application,module,registries,monitor這些引數是否為空,是否是GenericService型別的服務,檢查要註冊的bean的引用和方法等。雖然不知道這些是做什麼用的。

doExportUrls:很明顯,它這是獲取註冊中心的url路徑。因為dubbo支援多註冊中心,所以需要獲取多個註冊中心的url。

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-order-service&dubbo=2.5.7&pid=9720&registry=zookeeper&timestamp=1535884939183

doExportUrlsFor1Protocol:該方法是真正的開始暴露服務了,在這個方法裡有一大堆的引數在往map裡塞。這就是在為服務暴露做準備。dubbo的主線是Invoker物件,而url就是包含各種配置在內的一個協議字串。根據這個字串能夠做好多的事情。

doExportUrlsFor1Protocol方法很長,其中有幾處需要關注:

1、獲取主機地址,這段邏輯曾經在線上部署時,出現過問題。如今恍然大悟。在獲取主機地址的邏輯中:優先從配置裡獲取主機地址,否則從本地的InetAddress獲取地址,再者從Socket裡獲取地址。

2、本地服務不需要notify。即injvm是不需要進行notiry的。injvm是本地暴露的協議。類似:registry等協議。

   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();
        }

        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);

在一大堆引數配置完畢後,根據scope的配置決定是否要進行本地暴露。

本地暴露過程:本地暴露過程相對簡單,主要是通過擴充套件機制使用JavassitFactory對業務類進行動態代理生成一個動態代理物件,使用injvmProtocol把invoker包裝為一個Exporter。最後放到ServiceConfig的一個屬性exports裡。exports是一個list集合。

對於本地暴露的過程,為什麼要這樣進行暴露,以及包裝過程中都做了什麼,有什麼值得去包裝的呢?

根據我的理解,在這裡其實是一個IOC和AOP的體現,從Dubbo的擴充套件機制,實現了一個SPI自定義擴充套件,你可以加上無數個擴充套件。再加上Adaptive增強註解+裝飾機制達到了AOP的效果。對於這裡的包裝有什麼效果呢,目前知道和過濾器鏈有關。先打上個標記。

對於遠端暴露,和本地暴露也是同樣的過程,只不過Dubbo在把Invoker包裝為Exporter的過程中,做了兩件事情:

1、建立序列化和反序列化例項,根據擴充套件機制生成Server例項,預設使用NettyServer,還給Server添加了經過一系列包裝的handler物件,因為對網路程式設計編碼,解碼,Netty拆包,粘包,dubbo的執行緒池處理不瞭解,只能先到這裡了。總之建立Server例項完成之後,就返回了Exporter物件。

2、在返回Exporter物件之後,RegisterProtocol繼續發揮它的餘熱,但是卻也做了一個了不起的事情。接下來它需要把服務通過擴充套件機制得到的註冊中中心,把服務註冊到註冊中心上。

在返回Export物件之後,register第一個要做的事情就是打通註冊中心,本例為zookeeper,在看原始碼過程中,這裡會在zk上先建立一個/dubbo的節點,並生成一個監聽。然後返回回來後,依據提供者的配置,獲取url引數。接下來的事情就比較簡單了,根據zkClient直接在dubbo上建立節點就OK了。

還會呼叫registry的subscribe方法,這裡主要是加了註冊訂閱Listener,在創建出其他節點之後會呼叫notify方法。notify方法會做兩件事,1. 會將url改動更新到快取的配置檔案中。2. 會通知listener變動,此通知為全量通知

下面來看一下我的zookeeper的節點內容,從第一行看起,會看到在 /dubbo/com.wk.common.facade.OrderFacade下一共有4個節點,分別是 consumers,configurators,routers,providers。其中在providers下的內容見第一個紅框,根據顯示,可以看到providers下有一大堆的內容,想來這就是我們在dubbo上註冊的那個url嘍。

對於configurators和routers都是為空,相比應該是在dubbo控制檯上做一些處理,之後就會有內容了。也就是說,當這些節點有變化的時候,就會通知ZookeeperRegister發生變化。

從官方的說明來說,服務啟動的時候,會向providers目錄下寫入自己的url地址。估計我這裡的consumbers是以前消費者啟動寫入的。

暫時先這樣,有不同見解的,歡迎評論。