dubbo系列-服務(wù)暴露與消費

一膨疏、dubbo的分層架構(gòu)

1照藻、dubbo的分層架構(gòu)
在具體將dubbo服務(wù)暴露和消費之前,我們還是限流看下dubbo的分成架構(gòu)


dubbo分層架構(gòu)
dubbo分層架構(gòu)

Service和Config兩層可以認(rèn)為是API層魔策,主要提供給API使用者氓皱,使用者無須關(guān)心底層 的實現(xiàn),只需要配置和完成業(yè)務(wù)代碼即可续室;后面所有的層級合在一起栋烤,可以認(rèn)為是SPI層,主要提供給擴(kuò)展者使用挺狰,即用戶可以基于Dubb明郭。框架做定制性的二次開發(fā)丰泊,擴(kuò)展其功能薯定。Dubbo的擴(kuò)展能力非常強(qiáng),這也是Dubbo一直廣受歡迎的原因之一.Dubbo框架中的分層代表了不同的邏輯實現(xiàn)瞳购,它們是一個個組件话侄,這些組件構(gòu)成了整個Dubbo體系,在使用方角度更多接觸到的可能是配置苛败,更多底層構(gòu)件被抽象和隱藏了满葛。我們先整體上看一下dubbo的整體分層,這里不太理解的小伙伴不用著急罢屈,我們先有個整體概念嘀韧。隨著我們后面看完服務(wù)暴露和消費是細(xì)節(jié)流程,回過頭再來看這個分層模型缠捌,應(yīng)該就會容易理解很多锄贷。

二、dubbo服務(wù)暴露

2.1. 服務(wù)暴露整體流程

image.png

如圖所示:服務(wù)暴露的整體上分成兩步曼月,第一步將需要暴露的服務(wù)實例通過代理轉(zhuǎn)換成Invoker谊却,第二步會將invoker通過具體的協(xié)議(protocol)比如dubboProtocol協(xié)議遠(yuǎn)程暴露后轉(zhuǎn)換成Exporter。這里的Invoker對象可以簡單理解成一個真實的服務(wù)對象實例哑芹,最重要的是我們可以對它發(fā)起invoke調(diào)用炎辨,通過invoke調(diào)用就可以暴露和消費服務(wù)。Invoker可以是一個本地的實現(xiàn)(服務(wù)內(nèi)部通過jvm調(diào)用)聪姿,也可以說是一個遠(yuǎn)程實現(xiàn)(跨jvm調(diào)用)碴萧,還可能是一個集群實現(xiàn)(服務(wù)有多個提供者)。下面我們就來看一下服務(wù)暴露的流程細(xì)節(jié)末购。

2.2 dubbo與spring集成

在說dubbo服務(wù)入口之前破喻,我們先來看一下dubbo和spring集成的部分。
我們知道盟榴,不論是xml還是注解的形式曹质,duboo都有提供很多變遷,比如我們在服務(wù)暴露的過程中,會將我們提供出去的bean標(biāo)記成Service羽德,以服務(wù)端xml配置為例几莽,dubbo-provicer.xml:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- provider's application name, used for tracing dependency relationship -->
    <dubbo:application name="demo-provider"/>

    <dubbo:registry address="zookeeper://114.132.223.55:3888?backup=114.132.223.55,114.132.223.55" />

    <!-- use dubbo protocol to export service on port 20880 -->
    <dubbo:protocol name="dubbo"/>

    <!-- service implementation, as same as regular local bean -->
    <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>

    <!-- declare the service interface to be exported -->
    <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>
</beans>

從xml配置中我們可以看到,我們將demoService定義成了 dubbo:service 標(biāo)簽類型玩般。實際上银觅,dubbo中有自己的一套標(biāo)簽解析器,在spring啟動加載的時候坏为,將所有用到的dubbo標(biāo)簽解析成相關(guān)的bean,并進(jìn)行后續(xù)處理镊绪,比如服務(wù)暴露或消費等匀伏。而在服務(wù)暴露的過程中,就是將service 標(biāo)簽通過解析器解析成了ServiceBean進(jìn)行后續(xù)處理蝴韭,這部分代碼入口在DubboNamespaceHandler中够颠,代碼如下:

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    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("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
        registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.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 AnnotationBeanDefinitionParser());
    }

}

如圖所示:首先DubboNamespaceHandler 繼承了NamespaceHandlerSupport,在init方法(init方法執(zhí)行時機(jī):spring在獲取bean工廠的時候榄鉴,此時會加載bean的解析器)中通過registerBeanDefinitionParser 方法給spring設(shè)置我們自定義的dubbo標(biāo)簽解析器履磨,后面spring在加載bean的定義信息的時候,就可以按照我們的解析器去解析庆尘,例如截圖中的service 和 refrence剃诅,DubboBeanDefinitionParser就會將配置文件中的bean設(shè)置成不同類型,配置文件中的 service標(biāo)簽對應(yīng)的bean就會設(shè)置成 ServiceBean的類型驶忌,而對于這些bean矛辕,后續(xù)被創(chuàng)建以后,就會當(dāng)成服務(wù)被暴露出去付魔。下面我們來看下ServiceBean中是怎么走到服務(wù)暴露的邏輯的聊品。

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
        ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
        ApplicationEventPublisherAware {
    ......

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        SpringExtensionFactory.addApplicationContext(applicationContext);
        supportedApplicationListener = addApplicationListener(applicationContext, this);
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }

    @Override
    @SuppressWarnings({"unchecked", "deprecation"})
    public void afterPropertiesSet() throws Exception{
           ......
          //設(shè)置bubbo的各種配置中心、監(jiān)控中心等等
          ......
    }

    @Override
    public void export() {
        super.export();
        // Publish ServiceBeanExportedEvent
        publishExportEvent();
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

  1. 繼承了ServiceConfig 這個類几苍,主要為了后續(xù)暴露服務(wù)調(diào)用父類的export方法翻屈。
  2. 實現(xiàn)了 ApplicationContextAware,是獲取到 applicaltionContext上下文妻坝,
  3. 實現(xiàn)了 BeanNameAware伸眶,獲取到bean 的名稱
  4. 實現(xiàn)了 ApplicationEventPublisherAware是設(shè)置事件發(fā)布器
  5. 實現(xiàn)了ApplicationListener 接口,是在bean實例化完成之后惠勒,finishRefresh方法調(diào)用赚抡,在全部bean都實例完成之后,開始事件通知相應(yīng)的service 暴露服務(wù)纠屋。
    看到這里涂臣,我們基本上就看到了服務(wù)暴露的入口了,在spring完成bean實例化之后,事件通知響應(yīng)的service開始暴露服務(wù)赁遗,也就是在 onApplicationEvent中服務(wù)暴露入口 export署辉,具體暴露交給了父類serviceConfig。

2.3 服務(wù)暴露具體細(xì)節(jié)

為了弄清主流程岩四,一些旁枝末節(jié)我們在此就不做分析哭尝,代碼分析從 org.apache.dubbo.config.ServiceConfig#doExportUrls 開始。

    private void doExportUrls() {
        //加載注冊中心
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            ApplicationModel.initProviderModel(pathKey, providerModel);
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs){
        

        ......
        appendRuntimeParameters(map);
        appendParameters(map, metrics);
        appendParameters(map, application);
        appendParameters(map, module);
        // remove 'default.' prefix for configs from ProviderConfig
        // appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, provider);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
       .......
        /**
          * ①以上有所刪減剖煌,目的是構(gòu)造Url,其中url包含了暴露的服務(wù)路徑材鹦、方法、協(xié)議耕姊、主機(jī)桶唐、端口、參數(shù)等等
          */

        //根據(jù)scpoe配置來暴露服務(wù)茉兰,如果scpoe不配置尤泽,默認(rèn)本地和遠(yuǎn)程都會暴露
        String scope = url.getParameter(SCOPE_KEY);
        // don't export when none is configured
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            // export to local if the config is not remote (export to remote only when config is remote)
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
              /**
                * ②本地服務(wù)暴露
                */      
                exportLocal(url);
            }
            // export to remote if the config is not local (export to local only when config is local)
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (!isOnlyInJvm() && logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        //if protocol is only injvm ,not register
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                           /**
                             * ③如果配置了監(jiān)控地址,則服務(wù)調(diào)用信息會上報
                             */     
                            url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }

                        // For providers, this is used to enable custom proxy to generate invoker
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }
                        /**
                         * ④代理模式獲取invoker,默認(rèn)javassist
                         */
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                        /**
                         * ⑤暴露invoker
                         */
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                }
                /**
                  * ⑥處理沒有注冊中心場景规脸,直接暴露服務(wù)
                  */
                
                else {
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
                /**
                 * @since 2.7.0
                 * ServiceData Store
                 */
                MetadataReportService metadataReportService = null;
                if ((metadataReportService = getMetadataReportService()) != null) {
                    metadataReportService.publishProvider(url);
                }
            }
        }
}

通讀以上代碼坯约,在2.1 服務(wù)暴露主流程中我們提到,整個服務(wù)暴露的主干莫鸭,其實就是將 senviceConfig通過代理轉(zhuǎn)換成invoker闹丐,然后再通過具體協(xié)議將invoker暴露出去最后返回exporter的過程∏辏基于此妇智,我們來看注釋中的 ①- ⑥
①主要通過反射獲取配置對象并放到map中,用于構(gòu)造url參數(shù)氏身,這塊的代碼較長巍棱,為了突出主流程,有所刪減蛋欣。url參數(shù)中包括了很多參數(shù)信息航徙,像需要暴露的服務(wù)路徑、方法陷虎、協(xié)議到踏、主機(jī)、端口等
②主要是本地服務(wù)的暴露尚猿,通常dubbo會把遠(yuǎn)程服務(wù)用injvm協(xié)議再暴露一份窝稿,這樣消費方直接消費同一個jvm內(nèi)部的服務(wù),避免了跨網(wǎng)絡(luò)遠(yuǎn)程工通信凿掂。所以本地服務(wù)相比遠(yuǎn)程暴露來說伴榔,少了打開遠(yuǎn)程注冊中心端口通信的過程纹蝴,僅僅是把invoker保存在內(nèi)存中并返回exporter∽偕伲可以點進(jìn) exportLocal 方法具體看一下

    private void exportLocal(URL url) {
        URL local = URLBuilder.from(url)
                .setProtocol(LOCAL_PROTOCOL)
                .setHost(LOCALHOST_VALUE)
                .setPort(0)
                .build();
        Exporter<?> exporter = protocol.export(
                //通過javassist代理方式獲取實現(xiàn)類的代理
                PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
        exporters.add(exporter);
        logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
    }

③中主要追加監(jiān)控上報地址塘安,框架會在攔截器中執(zhí)行數(shù)據(jù)上報,這部分是可選的援奢。
④通過動態(tài)代理的方式創(chuàng)建代理對象兼犯,這部分是重點,就是將serviceConfig通過代理轉(zhuǎn)換成invoker集漾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末切黔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子具篇,更是在濱河造成了極大的恐慌绕娘,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栽连,死亡現(xiàn)場離奇詭異,居然都是意外死亡侨舆,警方通過查閱死者的電腦和手機(jī)秒紧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挨下,“玉大人熔恢,你說我怎么就攤上這事〕舭剩” “怎么了叙淌?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長愁铺。 經(jīng)常有香客問我鹰霍,道長,這世上最難降的妖魔是什么茵乱? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任茂洒,我火速辦了婚禮,結(jié)果婚禮上瓶竭,老公的妹妹穿的比我還像新娘督勺。我一直安慰自己,他們只是感情好斤贰,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布智哀。 她就那樣靜靜地躺著,像睡著了一般荧恍。 火紅的嫁衣襯著肌膚如雪瓷叫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音赞辩,去河邊找鬼雌芽。 笑死,一個胖子當(dāng)著我的面吹牛辨嗽,可吹牛的內(nèi)容都是我干的世落。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼糟需,長吁一口氣:“原來是場噩夢啊……” “哼屉佳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起洲押,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤武花,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后杈帐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體体箕,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年挑童,在試婚紗的時候發(fā)現(xiàn)自己被綠了累铅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡站叼,死狀恐怖娃兽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情尽楔,我是刑警寧澤投储,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站阔馋,受9級特大地震影響玛荞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜垦缅,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一冲泥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧壁涎,春花似錦凡恍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至竟坛,卻和暖如春闽巩,著一層夾襖步出監(jiān)牢的瞬間钧舌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工涎跨, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留洼冻,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓隅很,卻偏偏與公主長得像撞牢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子叔营,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

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