dubbo源碼分析6(服務暴露之本地暴露)

  前面我們做了一大堆的準備工作,包括dubbo是怎麼跟spring進行整合的,然後一步一步是怎麼找到啓動入口的,而且還知道了,由於我們的dubbo的版本是2.7.5,所以其實啓動的入口是DubboBootstrap類,一切的開始都從這個啓動器開始出發

  下面,開始我們這一次的開車之旅;

  提前須知:

  服務提供者肯定就是提供一個接口,然後供別的消費者使用的,那麼問題來了,服務提供者怎麼做才能提供給別的人使用呢?

  其實服務提供者在初始化的時候,做了6件事(假設註冊中心是zookeeper)

  (1)  啓動容器(就是spring的ioc容器)

  (2)暴露服務(本地暴露和遠程暴露)

  (3)開啓netty服務端

  (4)連接zookeeper

  (5)將服務信息寫入zookeeper

     (6)監聽zookeeper節點(或者說訂閱服務信息)

  

  只要做完了這6件事情,一個新鮮可口的服務就可以使用了,我們可以使用官方的圖來看看,下圖所示,其實就是完成了下圖中的步驟0和步驟1

 

1.啓動容器

  大家猜猜我們啓動一個dubbo服務需要些什麼東西?需要tomcat嗎?  

  其實啓動dubbo服務的話,我們只需要有spring+註冊中心就可以了,其他的都不需要,爲什麼不需要tomcat這種servlet容器啊?

  你想想啊,我們在服務提供者這裏,使用到了Controller了麼?使用到了http協議了麼?都沒有吧,而且如果dubbo需要使用servlet容器,那麼還需要額外消耗內存,性能也會下降

  dubbo啓動方式有三種

  1.1  servlet容器:這種是最消耗性能的,我們肯定不會使用

  1.2 自己寫一個main方法運行(Spring容器):官方demo使用的就是這種方式,下圖所示,其實就是我們自己創建一個spring容器,去啓動,就能加載自定義的配置文件,這種方式適用於測試和調試(我們看源碼就是使用這種啓動方式)

 

  1.3 官方提供的啓動方式:org.apache.dubbo.container.Main類有個main方法,這裏可以啓動,而且可以看到默認使用的是SpringContainer

 

  我們看看SpringContainer類中的start()方法,發現也是sping容器啓動的

 

  如果到了這裏你還是不會啓動,官方還提供了腳本方式啓動,腳本給你準備好了,可以支持你的服務在linux中去啓動

 

  總結一下,有三種啓動dubbo服務的方式,第一種是使用tomcat等servlet容器去啓動,第二種是我們自己簡單的使用一下spring容器加載配置文件,第三種就是官方提供的Main類,內部對spring容器的啓動和停止進行了更好的封裝,並提供了一系列的服務端啓動腳本

  開發調試的時候可以使用第二種,生產上線使用第三種,第一種狗都不用( ̄▽ ̄)ノ,哈哈哈哈

 

2.暴露服務(本地暴露和遠程暴露)

  記得前面說過,暴露服務分爲本地暴露和遠程暴露,再說明一下,爲什麼會有本地暴露?

  就是當前同一個jvm中有其他的服務要使用到當前的服務,肯定不會去走註冊中心調用服務呀(就類似於你家開餐館的,難道你喫東西會使用美團外賣點自己家的菜麼?生草๑乛◡乛๑) 

  我們還是從DubboBootstrap的start()方法開始看:

 

 

  看看下面SerrviceBean的類繼承圖,這個export()方法在ServiceConfig類中

 

 

  繼續跟進這個doExport()方法

 

 

 

  上面說了一大堆的垃圾話,接下來纔是重點,由於下面這個方法太長,我把代碼拷貝下來,然後大篇幅的省略一些代碼

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
       
       //省略了好多好多代碼

        String scope = url.getParameter(SCOPE_KEY);
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            //1.本地暴露服務
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            //2.遠程暴露服務
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                //2.1如果存在註冊中心,就把服務暴露到註冊中心
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {

                        //省略代碼

                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        //2.1.1 加載dubbo的監控中心,如果有監控中心
                        //就將配置添加到url中
                        URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                        }
                        //省略一些代碼
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }

                        //2.1.2 這裏將需要導出的服務封裝成Invoker對象
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                        //2.1.3 將invoker導出到註冊中心,並生成exporter
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        //2.1.4 將export收集
                        exporters.add(exporter);
                    }
                    //2.2 沒有服務註冊中心,就採用直連的方式導出服務,不需要配置監聽,直接將invoker暴露到註冊中心,並生成exporter
                } else {
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
               //省略代碼
            }
        }
        this.urls.add(url);
    }

  

  接下來又是一個很重要的地方!!!這個invoker表示一個什麼呀?

  我們首先需要將日誌級別修改一下:

 

  然後重新啓動調試,根據控制檯打印的ProxyFactory$Adaptive類和,自己去對應的包下新建目錄,把控制檯的代碼拷貝過去

  最後將日誌級別改爲info,重新啓動調試就好了,嘿嘿!

  沒辦法,就是要這樣奇葩的操作,才能在等會兒調試的時候纔會進入到這兩個類裏面(-_-メ)

 

 

3.本地暴露服務

  進入到ServiceConfig的doExportUrlsFor1Protocol()方法,然後下圖的exportLocal(url)方法

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
       
       //省略了好多好多代碼

        String scope = url.getParameter(SCOPE_KEY);
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            //1.本地暴露服務
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }

 

  注意下圖中URL local,是以injvm開頭的,說明是暴露在本地jvm中的(後續會說到的,如果是暴露在遠程的,那就是registry開頭的)

 

  在上圖的getInvoker方法,就是進入到之前我們自己創建的那個類中,下面就貼一下好多代碼當流程圖看了(-_-メ)

 

 

 

  最後就是進入到InjvmExporter的構造器了

  注意:這裏的 exporterMap對這個exporter做了緩存, 後面這個緩存map會很有用的

 

4.總結

  根據服務暴露到jvm的流程,總結出來就是下面這個圖,應該很清晰了,就是將當前服務提供者中的服務首先使用代理工廠封裝成Invoker,然後調用Protocal的export()方法,將invoker轉換爲Exporter,並且還會緩存一份這個Exporter在exporterMap中;

  後面暴露遠程服務會稍微比這個複雜一丟丟,就是增加了本地啓動netty的步驟和遠程操作zookeeper創建節點以及監聽zookeeper的變化

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章