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

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

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


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

Service和Config兩層可以認(rèn)為是API層,主要提供給API使用者,使用者無(wú)須關(guān)心底層 的實(shí)現(xiàn),只需要配置和完成業(yè)務(wù)代碼即可;后面所有的層級(jí)合在一起,可以認(rèn)為是SPI層,主要提供給擴(kuò)展者使用,即用戶可以基于Dubb??蚣茏龆ㄖ菩缘亩伍_發(fā),擴(kuò)展其功能。Dubbo的擴(kuò)展能力非常強(qiáng),這也是Dubbo一直廣受歡迎的原因之一.Dubbo框架中的分層代表了不同的邏輯實(shí)現(xiàn),它們是一個(gè)個(gè)組件,這些組件構(gòu)成了整個(gè)Dubbo體系,在使用方角度更多接觸到的可能是配置,更多底層構(gòu)件被抽象和隱藏了。我們先整體上看一下dubbo的整體分層,這里不太理解的小伙伴不用著急,我們先有個(gè)整體概念。隨著我們后面看完服務(wù)暴露和消費(fèi)是細(xì)節(jié)流程,回過頭再來看這個(gè)分層模型,應(yīng)該就會(huì)容易理解很多。

二、dubbo服務(wù)暴露

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

image.png

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

2.2 dubbo與spring集成

在說dubbo服務(wù)入口之前,我們先來看一下dubbo和spring集成的部分。
我們知道,不論是xml還是注解的形式,duboo都有提供很多變遷,比如我們?cè)诜?wù)暴露的過程中,會(huì)將我們提供出去的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)簽類型。實(shí)際上,dubbo中有自己的一套標(biāo)簽解析器,在spring啟動(dòng)加載的時(shí)候,將所有用到的dubbo標(biāo)簽解析成相關(guān)的bean,并進(jìn)行后續(xù)處理,比如服務(wù)暴露或消費(fèi)等。而在服務(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í)行時(shí)機(jī):spring在獲取bean工廠的時(shí)候,此時(shí)會(huì)加載bean的解析器)中通過registerBeanDefinitionParser 方法給spring設(shè)置我們自定義的dubbo標(biāo)簽解析器,后面spring在加載bean的定義信息的時(shí)候,就可以按照我們的解析器去解析,例如截圖中的service 和 refrence,DubboBeanDefinitionParser就會(huì)將配置文件中的bean設(shè)置成不同類型,配置文件中的 service標(biāo)簽對(duì)應(yīng)的bean就會(huì)設(shè)置成 ServiceBean的類型,而對(duì)于這些bean,后續(xù)被創(chuàng)建以后,就會(huì)當(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 這個(gè)類,主要為了后續(xù)暴露服務(wù)調(diào)用父類的export方法。
  2. 實(shí)現(xiàn)了 ApplicationContextAware,是獲取到 applicaltionContext上下文,
  3. 實(shí)現(xiàn)了 BeanNameAware,獲取到bean 的名稱
  4. 實(shí)現(xiàn)了 ApplicationEventPublisherAware是設(shè)置事件發(fā)布器
  5. 實(shí)現(xiàn)了ApplicationListener 接口,是在bean實(shí)例化完成之后,finishRefresh方法調(diào)用,在全部bean都實(shí)例完成之后,開始事件通知相應(yīng)的service 暴露服務(wù)。
    看到這里,我們基本上就看到了服務(wù)暴露的入口了,在spring完成bean實(shí)例化之后,事件通知響應(yīng)的service開始暴露服務(wù),也就是在 onApplicationEvent中服務(wù)暴露入口 export,具體暴露交給了父類serviceConfig。

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

為了弄清主流程,一些旁枝末節(jié)我們?cè)诖司筒蛔龇治?,代碼分析從 org.apache.dubbo.config.ServiceConfig#doExportUrls 開始。

    private void doExportUrls() {
        //加載注冊(cè)中心
        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)程都會(huì)暴露
        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)用信息會(huì)上報(bà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);
                    }
                }
                /**
                  * ⑥處理沒有注冊(cè)中心場(chǎng)景,直接暴露服務(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ù)暴露主流程中我們提到,整個(gè)服務(wù)暴露的主干,其實(shí)就是將 senviceConfig通過代理轉(zhuǎn)換成invoker,然后再通過具體協(xié)議將invoker暴露出去最后返回exporter的過程?;诖耍覀儊砜醋⑨屩械?①- ⑥
①主要通過反射獲取配置對(duì)象并放到map中,用于構(gòu)造url參數(shù),這塊的代碼較長(zhǎng),為了突出主流程,有所刪減。url參數(shù)中包括了很多參數(shù)信息,像需要暴露的服務(wù)路徑、方法、協(xié)議、主機(jī)、端口等
②主要是本地服務(wù)的暴露,通常dubbo會(huì)把遠(yuǎn)程服務(wù)用injvm協(xié)議再暴露一份,這樣消費(fèi)方直接消費(fèi)同一個(gè)jvm內(nèi)部的服務(wù),避免了跨網(wǎng)絡(luò)遠(yuǎn)程工通信。所以本地服務(wù)相比遠(yuǎn)程暴露來說,少了打開遠(yuǎn)程注冊(cè)中心端口通信的過程,僅僅是把invoker保存在內(nèi)存中并返回exporter。可以點(diǎn)進(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代理方式獲取實(shí)現(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)控上報(bào)地址,框架會(huì)在攔截器中執(zhí)行數(shù)據(jù)上報(bào),這部分是可選的。
④通過動(dòng)態(tài)代理的方式創(chuàng)建代理對(duì)象,這部分是重點(diǎn),就是將serviceConfig通過代理轉(zhuǎn)換成invoker。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容