日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯網營銷解決方案
深入解析Dubbo3.0服務端暴露全流程

深入解析 Dubbo 3.0 服務端暴露全流程

作者:熊聘 2021-09-06 09:46:26

開發(fā)

后端

云計算 隨著云原生時代的到來,Dubbo 3.0 的一個很重要的目標就是全面擁抱云原生。正因如此,Dubbo 3.0 為了能夠更好的適配云原生,將原來的接口級服務發(fā)現機制演進為應用級服務發(fā)現機制。

創(chuàng)新互聯公司專注于企業(yè)全網營銷推廣、網站重做改版、桂陽網站定制設計、自適應品牌網站建設、H5建站成都商城網站開發(fā)、集團公司官網建設、成都外貿網站建設公司、高端網站制作、響應式網頁設計等建站業(yè)務,價格優(yōu)惠性價比高,為桂陽等各大城市提供網站開發(fā)制作服務。

背景

隨著云原生時代的到來,Dubbo 3.0 的一個很重要的目標就是全面擁抱云原生。正因如此,Dubbo 3.0 為了能夠更好的適配云原生,將原來的接口級服務發(fā)現機制演進為應用級服務發(fā)現機制。

基于應用級服務發(fā)現機制,Dubbo 3.0 能大幅降低框架帶來的額外資源消耗,大幅提升資源利用率,主要體現在:

單機常駐內存下降 75%

能支持的集群實例規(guī)模以百萬計的集群
注冊中心總體數據量下降超 90%
目前關于 Dubbo 服務端暴露流程的技術文章很多,但是都是基于 Dubbo 接口級服務發(fā)現機制來解讀的。在 Dubbo 3.0 的應用級服務發(fā)現機制下,服務端暴露流程與之前有很大的變化,本文希望可以通過 對Dubbo 3.0 源碼理解來解析服務端暴露全流程。

什么是應用級服務發(fā)現

簡單來說,以前 Dubbo 是將接口的信息全部注冊到注冊中心,而一個應用實例一般會存在多個接口,這樣一來注冊的數據量就要大很多,而且有冗余。應用級服務發(fā)現的機制是同一個應用實例僅在注冊中心注冊一條數據,這種機制主要解決以下幾個問題:

對齊主流微服務模型,如:Spring Cloud
支持 Kubernetes native service,Kubernetes 中維護調度的服務都是基于應用實例級,不支持接口級
減少注冊中心數據存儲能力,降低了地址變更推送的壓力
假設應用 dubbo-application 部署了 3 個實例(instance1, instance2, instance3),并且對外提供了 3 個接口(sayHello, echo, getVersion)分別設置了不同的超時時間。在接口級和應用級服務發(fā)現機制下,注冊到注冊中心的數據是截然不同的。如下圖所示:

接口級服務發(fā)現機制下注冊中心中的數據

  
 
 
 
  1. "sayHello": [  {"application":"dubbo-application","name":"instance1", "ip":"127.0.0.1", "metadata":{"timeout":1000}},  {"application":"dubbo-application","name":"instance2", "ip":"127.0.0.2", "metadata":{"timeout":2000}},  {"application":"dubbo-application","name":"instance3", "ip":"127.0.0.3", "metadata":{"timeout":3000}},],"echo": [  {"application":"dubbo-application","name":"instance1", "ip":"127.0.0.1", "metadata":{"timeout":1000}},  {"application":"dubbo-application","name":"instance2", "ip":"127.0.0.2", "metadata":{"timeout":2000}},  {"application":"dubbo-application","name":"instance3", "ip":"127.0.0.3", "metadata":{"timeout":3000}},],"getVersion": [  {"application":"dubbo-application","name":"instance1", "ip":"127.0.0.1", "metadata":{"timeout":1000}},  {"application":"dubbo-application","name":"instance2", "ip":"127.0.0.2", "metadata":{"timeout":2000}},  {"application":"dubbo-application","name":"instance3", "ip":"127.0.0.3", "metadata":{"timeout":3000}}] 

應用級服務發(fā)現機制下注冊中心中的數據

  
 
 
 
  1. "dubbo-application": [  {"name":"instance1", "ip":"127.0.0.1", "metadata":{"timeout":1000}},  {"name":"instance2", "ip":"127.0.0.2", "metadata":{"timeout":2000}},  {"name":"instance3", "ip":"127.0.0.3", "metadata":{"timeout":3000}}] 

通過對比我們可以發(fā)現,采用應用級服務發(fā)現機制確實使注冊中心中的數據量減少了很多,那些原有的接口級的數據存儲在元數據中心中。

服務端暴露全流程

引入應用級服務發(fā)現機制以后,Dubbo 3.0 服務端暴露全流程和之前有很大的區(qū)別。暴露服務端全流程的核心代碼在 DubboBootstrap#doStart 中,具體如下:

  
 
 
 
  1. private void doStart() {    // 1. 暴露Dubbo服務    exportServices();    // If register consumer instance or has exported services    if (isRegisterConsumerInstance() || hasExportedServices()) {        // 2. 暴露元數據服務        exportMetadataService();        // 3. 定時更新和上報元數據        registerServiceInstance();        ....    }    ......} 

假設以 Zookeeper 作為注冊中,對外暴露 Triple 協(xié)議的服務為例,服務端暴露全流程時序圖如下:

我們可以看到,整個的暴露流程還是挺復雜的,一共可以分為四個部分:

暴露 injvm 協(xié)議的服務
注冊 service-discovery-registry 協(xié)議
暴露 Triple 協(xié)議的服務并注冊 registry 協(xié)議
暴露 MetadataService 服務
下面會分別從這四個部分對服務暴露全流程進行詳細講解。

1、暴露 injvm 協(xié)議的服務

injvm 協(xié)議的服務是暴露在本地的,主要原因是在一個應用上往往既有 Service(暴露服務)又有 Reference(服務引用)的情況存在,并且 Reference 引用的服務就是在該應用上暴露的 Service。為了支持這種使用場景,Dubbo 提供了 injvm 協(xié)議,將 Service 暴露在本地,Reference 就可以不需要走網絡直接在本地調用 Service。

整體時序圖

由于這部分內容在之前的接口級服務發(fā)現機制中是類似的,所以相關的核心代碼就不在這里展開討論了。

2、注冊 service-discovery-registry 協(xié)議

注冊 service-discovery-registry 協(xié)議的核心目的是為了注冊與服務相關的元數據,默認情況下元數據通過 InMemoryWritableMetadataService 將數據存儲在本地內存和本地文件。

整體時序圖

核心代碼在 ServiceConfig#exportRemote 中,具體如下:

注冊 service-discovery-registry 協(xié)議的入口

  
 
 
 
  1. private URL exportRemote(URL url, List registryURLs) {    if (CollectionUtils.isNotEmpty(registryURLs)) {        // 如果是多個注冊中心,通過循環(huán)對每個注冊中心進行注冊        for (URL registryURL : registryURLs) {            // 判斷是否是service-discovery-registry協(xié)議            // 將service-name-mapping參數的值設置為true            if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) {                url = url.addParameterIfAbsent(SERVICE_NAME_MAPPING_KEY, "true");            }            ......            // 注冊service-discovery-registry協(xié)議復用服務暴露流程            doExportUrl(registryURL.putAttribute(EXPORT_KEY, url), true);        }    ......    return url;} 

invoker 中包裝 Metadata

核心代碼在 ServiceConfig#doExportUrl 中,具體如下:

  
 
 
 
  1. private void doExportUrl(URL url, boolean withMetaData) {    Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);    // 此時的withMetaData的值為true    // 將invoker包裝成DelegateProviderMetaDataInvoker    if (withMetaData) {        invoker = new DelegateProviderMetaDataInvoker(invoker, this);    }    Exporter exporter = PROTOCOL.export(invoker);    exporters.add(exporter);} 

通過 RegistryProtocol 將 Invoker 轉化成 Exporter

核心代碼在 ProtocolListenerWrapper#export 中,具體如下:

  
 
 
 
  1. public  Exporter export(Invoker invoker) throws RpcException {    // 此時的protocol為RegistryProtocol類型    if (UrlUtils.isRegistry(invoker.getUrl())) {        return protocol.export(invoker);    }    ......} 

RegistryProtocol 將 Invoker 轉化成 Exporter 的核心流程

核心代碼在 RegistryProtocol#export 中,具體如下:

  
 
 
 
  1. public  Exporter export(final Invoker originInvoker) throws RpcException {    URL registryUrl = getRegistryUrl(originInvoker);    URL providerUrl = getProviderUrl(originInvoker);    ......    // 再次暴露Triple協(xié)議的服務    final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl);    // registryUrl中包含service-discovery-registry協(xié)議    // 通過該協(xié)議創(chuàng)建ServiceDiscoveryRegistry對象    // 然后組合RegistryServiceListener監(jiān)聽器,    // 最后包裝成ListenerRegistryWrapper對象    final Registry registry = getRegistry(registryUrl);    final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);    boolean register = providerUrl.getParameter(REGISTER_KEY, true);    if (register) {        // 注冊service-discovery-registry協(xié)議        // 觸發(fā)RegistryServiceListener的onRegister事件        register(registry, registeredProviderUrl);    }    ......    // 觸發(fā)RegistryServiceListener的onRegister事件    notifyExport(exporter);    return new DestroyableExporter<>(exporter);} 

暴露 Triple 協(xié)議的服務

核心代碼在 RegistryProtocol#doLocalExport 中,具體如下:

  
 
 
 
  1. private  ExporterChangeableWrapper doLocalExport(final Invoker originInvoker, URL providerUrl) {    String key = getCacheKey(originInvoker);    // 此時的protocol為Triple協(xié)議的代理類    // 和暴露injvm協(xié)議的PROTOCOL相同    return (ExporterChangeableWrapper) bounds.computeIfAbsent(key, s -> {        Invoker invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);        return new ExporterChangeableWrapper<>((Exporter) protocol.export(invokerDelegate), originInvoker);    });} 

注冊service-discovery-registry協(xié)議

核心代碼在 ServiceDiscoveryRegistry#register和ServiceDiscoveryRegistry#doRegister 中,具體如下:

1、ServiceDiscoveryRegistry#register

  
 
 
 
  1. public final void register(URL url) {    // 只有服務端(Provider)才需要注冊    if (!shouldRegister(url)) {        return;    }    // 注冊service-discovery-registry協(xié)議    doRegister(url);} 

2、ServiceDiscoveryRegistry#doRegister

  
 
 
 
  1. public void doRegister(URL url) {    url = addRegistryClusterKey(url);    // 注冊元數據    if (writableMetadataService.exportURL(url)) {        if (logger.isInfoEnabled()) {            logger.info(format("The URL[%s] registered successfully.", url.toString()));        }    } else {        if (logger.isWarnEnabled()) {            logger.warn(format("The URL[%s] has been registered.", url.toString()));        }    }} 

注冊元數據

核心代碼在 InMemoryWritableMetadataService#exportURL 中,具體如下:

  
 
 
 
  1. public boolean exportURL(URL url) {    // 如果是MetadataService,則不注冊元數據    if (MetadataService.class.getName().equals(url.getServiceInterface())) {        this.metadataServiceURL = url;        return true;    }    updateLock.readLock().lock();    try {        String[] clusters = getRegistryCluster(url).split(",");        for (String cluster : clusters) {            MetadataInfo metadataInfo = metadataInfos.computeIfAbsent(cluster, k -> new MetadataInfo(ApplicationModel.getName()));            // 將Triple協(xié)議的服務中接口相關的數據生成ServiceInfo            // 將ServiceInfo注冊到MetadataInfo中            metadataInfo.addService(new ServiceInfo(url));        }        metadataSemaphore.release();        return addURL(exportedServiceURLs, url);    } finally {        updateLock.readLock().unlock();    }} 

發(fā)布 onRegister 事件

核心代碼在 ListenerRegistryWrapper#register 中,具體如下:

  
 
 
 
  1. public void register(URL url) {    try {        // registry為ServiceDiscoveryRegistry對象        // 此時已經調用完ServiceDiscoveryRegistry#registry方法        registry.register(url);    } finally {        if (CollectionUtils.isNotEmpty(listeners) && !UrlUtils.isConsumer(url)) {            RuntimeException exception = null;            for (RegistryServiceListener listener : listeners) {                if (listener != null) {                    try {                        // 注冊完service-discovery-registry協(xié)議后發(fā)布onRegister事件                        listener.onRegister(url, registry);                    } catch (RuntimeException t) {                        logger.error(t.getMessage(), t);                        exception = t;                    }                }            }            if (exception != null) {                throw exception;            }        }    }} 

發(fā)布服務注冊事件

核心代碼在 RegistryProtocol#notifyExport 中,具體如下:

  
 
 
 
  1. private  void notifyExport(ExporterChangeableWrapper exporter) {    List listeners = ExtensionLoader.getExtensionLoader(RegistryProtocolListener.class)        .getActivateExtension(exporter.getOriginInvoker().getUrl(), "registry.protocol.listener");    if (CollectionUtils.isNotEmpty(listeners)) {        for (RegistryProtocolListener listener : listeners) {            // 發(fā)布RegistryProtocolListener的onExport事件            listener.onExport(this, exporter);        }    }} 

我們可以看出注冊 service-discovery-registry 協(xié)議的核心目的是為了將服務的接口相關的信息存儲在內存中。從兼容性和平滑遷移兩方面來考慮,社區(qū)在實現的時候采取復用 ServiceConfig 的暴露流程的方式。

3、暴露Triple協(xié)議服務并注冊registry協(xié)議

暴露 Triple 協(xié)議的服務并注冊 registry 協(xié)議是 Dubbo 3.0 服務暴露的核心流程,一共分為兩部分:

暴露 Triple 協(xié)議的服務

注冊 registry 協(xié)議
由于暴露 Triple 協(xié)議服務的流程和暴露 Injvm 協(xié)議服務的流程是一致的,所以不再贅述。注冊 registry 協(xié)議的過程僅僅注冊了應用實例相關的信息,也就是之前提到的應用級服務發(fā)現機制。

整體時序圖

通過 InterfaceCompatibleRegistryProtocol 將 Invoker 轉化成 Exporter

核心代碼在 ProtocolListenerWrapper#export 中,具體如下:

  
 
 
 
  1. public  Exporter export(Invoker invoker) throws RpcException {    // 此時的protocol為InterfaceCompatibleRegistryProtocol類型(繼承了RegistryProtocol)    // 注意:在注冊service-discovery-registry協(xié)議的時候protocol為RegistryProtocol類型    if (UrlUtils.isRegistry(invoker.getUrl())) {        return protocol.export(invoker);    }    ......} 

RegistryProtocol 將 Invoker 轉化成 Exporter 的核心流程

核心代碼在 RegistryProtocol#export 中,具體如下:

  
 
 
 
  1. public  Exporter export(final Invoker originInvoker) throws RpcException {    URL registryUrl = getRegistryUrl(originInvoker);    URL providerUrl = getProviderUrl(originInvoker);    ......    // 再次暴露Triple協(xié)議的服務    final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl);    // registryUrl中包含registry協(xié)議    // 通過該協(xié)議創(chuàng)建ZookeeperRegistry對象    // 然后組合RegistryServiceListener監(jiān)聽器,    // 最后包裝成ListenerRegistryWrapper對象    // 注意:    // 1. service-discovery-registry協(xié)議對應的是ServiceDiscoveryRegistry    // 2. registry協(xié)議對應的是ZookeeperRegistry    final Registry registry = getRegistry(registryUrl);    final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);    boolean register = providerUrl.getParameter(REGISTER_KEY, true);    if (register) {        // 注冊registry協(xié)議        // 觸發(fā)RegistryServiceListener的onRegister事件        register(registry, registeredProviderUrl);    }    ......    // 發(fā)布RegistryProtocolListener的onExport事件    notifyExport(exporter);    return new DestroyableExporter<>(exporter);} 

注冊 registry 協(xié)議

核心代碼在 FailbackRegistry#register 和 ServiceDiscoveryRegistry#doRegister 中(ZookeeperRegistry 繼承 FailbackRegistry)中,具體如下:

1、FailbackRegistry#register

  
 
 
 
  1. public void register(URL url) {    if (!acceptable(url)) {        ......        try {            // 注冊registry協(xié)議            doRegister(url);        } catch (Exception e) {            ......        }    }} 

2、ServiceDiscoveryRegistry#doRegister

  
 
 
 
  1. public void doRegister(URL url) {    try {        // 在zookeeper上注冊Provider        // 目錄:/dubbo/xxxService/providers/***        // 數據:dubbo://192.168.31.167:20800/xxxService?anyhost=true&        //      application=application-name&async=false&deprecated=false&dubbo=2.0.2&        //      dynamic=true&file.cache=false&generic=false&interface=xxxService&        //      metadata-type=remote&methods=hello&pid=82470&release=&        //      service-name-mapping=true&side=provider×tamp=1629588251493        zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));    } catch (Throwable e) {        throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);    }} 

訂閱地址變更

核心代碼在 FailbackRegistry#subscribe 和 ZookeeperRegistry#doSubscribe 中,具體如下:

1、FailbackRegistry#subscribe

  
 
 
 
  1. public void subscribe(URL url, NotifyListener listener) {    ......    try {        // 調用ZookeeperRegistry#doSubscribe        doSubscribe(url, listener);    } catch (Exception e) {    ......} 

2、ZookeeperRegistry#doSubscribe

  
 
 
 
  1. public void doSubscribe(final URL url, final NotifyListener listener) {    try {        if (ANY_VALUE.equals(url.getServiceInterface())) {            ......        } else {            ......            for (String path : toCategoriesPath(url)) {                ConcurrentMap listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());                ChildListener zkListener = listeners.computeIfAbsent(listener, k -> new RegistryChildListenerImpl(url, path, k, latch));                if (zkListener instanceof RegistryChildListenerImpl) {                    ((RegistryChildListenerImpl) zkListener).setLatch(latch);                }                // 創(chuàng)建臨時節(jié)點用來存儲configurators數據                // 目錄:/dubbo/xxxService/configurators                // 數據:應用的配置信息,可以在dubbo-admin中進行修改,默認為空                zkClient.create(path, false);                // 添加監(jiān)聽器,用來監(jiān)聽configurators中的變化                List children = zkClient.addChildListener(path, zkListener);                if (children != null) {                    urls.addAll(toUrlsWithEmpty(url, path, children));                }            }            ......        }    } catch (Throwable e) {        ......    } 

建立暴露的 Triple 協(xié)議服務與 Metadata 之間的聯系
核心代碼在 ServiceConfig#exportUrl、MetadataUtils#publishServiceDefinition、InMemoryWritableMetadataService#publishServiceDefinition、RemoteMetadataServiceImpl#publishServiceDefinition 和 MetadataReport#storeProviderMetadata 中,具體如下:

1、ServiceConfig#exportUrl

  
 
 
 
  1. private void exportUrl(URL url, List registryURLs) {    ......    if (!SCOPE_NONE.equalsIgnoreCase(scope)) {        ......        if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {            url = exportRemote(url, registryURLs);            // 發(fā)布事件,更新服務接口相關的數據            MetadataUtils.publishServiceDefinition(url);        }    }    ......} 

2、MetadataUtils#publishServiceDefinition

  
 
 
 
  1. public static void publishServiceDefinition(URL url) {    // 將服務接口相關的數據存在到InMemoryWritableMetadataService中    WritableMetadataService.getDefaultExtension().publishServiceDefinition(url);    // 將服務接口相關的數據存在到遠端的元數據中心    if (REMOTE_METADATA_STORAGE_TYPE.equalsIgnoreCase(url.getParameter(METADATA_KEY))) {        getRemoteMetadataService().publishServiceDefinition(url);    }} 

3、InMemoryWritableMetadataService#publishServiceDefinition

  
 
 
 
  1. public void publishServiceDefinition(URL url) {    try {        String interfaceName = url.getServiceInterface();        if (StringUtils.isNotEmpty(interfaceName)            && !ProtocolUtils.isGeneric(url.getParameter(GENERIC_KEY))) {            Class interfaceClass = Class.forName(interfaceName);            ServiceDefinition serviceDefinition = ServiceDefinitionBuilder.build(interfaceClass);            Gson gson = new Gson();            String data = gson.toJson(serviceDefinition);            // 存儲服務接口相關數據            // 數據格式:            // {            //   "canonicalName": "xxxService",            //   "codeSource": "file:/Users/xxxx",            //   "methods": [{            //       "name": "hello",            //       "parameterTypes": ["java.lang.String"],            //       "returnType": "java.lang.String",            //       "annotations": []            //   }],            //   "types": [{            //       "type": "java.lang.String"            //    }],            //  "annotations": []            // }             serviceDefinitions.put(url.getServiceKey(), data);            return;        } else if (CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY))) {            ......        }        ......    } catch (Throwable e) {        ......    }} 

4、RemoteMetadataServiceImpl#publishServiceDefinition

  
 
 
 
  1. public void publishServiceDefinition(URL url) {    checkRemoteConfigured();    String side = url.getSide();    if (PROVIDER_SIDE.equalsIgnoreCase(side)) {        // 發(fā)布服務端(Provider)的服務接口信息到元數據中心        publishProvider(url);    } else {        ......    }}RemoteMetadataServiceImpl#publishProviderprivate void publishProvider(URL providerUrl) throws RpcException {    ......    try {        String interfaceName = providerUrl.getServiceInterface();        if (StringUtils.isNotEmpty(interfaceName)) {            ......            for (Map.Entry entry : getMetadataReports().entrySet()) {                // 獲取MetadataReport服務,該服務用來訪問元數據中心                MetadataReport metadataReport = entry.getValue();                // 將服務接口信息存儲到元數據中心                metadataReport.storeProviderMetadata(new MetadataIdentifier(providerUrl.getServiceInterface(),                    providerUrl.getVersion(), providerUrl.getGroup(),                    PROVIDER_SIDE, providerUrl.getApplication()), fullServiceDefinition);            }            return;        }        ......    } catch (ClassNotFoundException e) {        ......    }} 

5、AbstractMetadataReport#storeProviderMetadata

  
 
 
 
  1. public void storeProviderMetadata(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition){    if (syncReport) {        storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition);    } else {        // 異步存儲到元數據中心        reportCacheExecutor.execute(() -> storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition));    }}private void storeProviderMetadataTask(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) {    try {        ......        allMetadataReports.put(providerMetadataIdentifier, serviceDefinition);        failedReports.remove(providerMetadataIdentifier);        Gson gson = new Gson();        // data的數據格式:        // {        //   "parameters": {        //       "side": "provider",         //       "interface": "xxxService",        //       "metadata-type": "remote",        //       "service-name-mapping": "true",        //   },        //   "canonicalName": "xxxService",        //   "codeSource": "file:/Users/xxxx",        //   "methods": [{        //       "name": "hello",        //       "parameterTypes": ["java.lang.String"],        //       "returnType": "java.lang.String",        //       "annotations": []        //   }],        //   "types": [{        //       "type": "java.lang.String"        //    }],        //  "annotations": []        // }         String data = gson.toJson(serviceDefinition);        // 存儲到元數據中心,實例中的元數據中心是ZookeeperMetadataReport        // 目錄:元數據中心Metadata-report的/dubbo/metadata/xxxService/provider/${application-name}節(jié)點下        doStoreProviderMetadata(providerMetadataIdentifier, data);        // 存儲到本地文件        // 路徑:xxxService:::provider:${application-name}         saveProperties(providerMetadataIdentifier, data, true, !syncReport);    } catch (Exception e) {        ......    }} 

建立 Triple 協(xié)議服務與 MetadataReport 服務之間的關系
核心代碼在 ServiceConfig#exported、MetadataServiceNameMapping#map 和 ZookeeperMetadataReport#registerServiceAppMapping 中,具體如下:

1、ServiceConfig#exported

  
 
 
 
  1. protected void exported() {    exported = true;    List exportedURLs = this.getExportedUrls();    exportedURLs.forEach(url -> {        // 判斷URL中是否標記有service-name-mapping的字段        // 標記有該字段的服務是需要將暴露的服務與元數據中心關聯起來        // Consumer可以通過元數據中心的消息變更感知到Provider端元數據的變更        if (url.getParameters().containsKey(SERVICE_NAME_MAPPING_KEY)) {            ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension();            // 建立關系            serviceNameMapping.map(url);        }    });    onExported();} 

2、MetadataServiceNameMapping#map

  
 
 
 
  1. public void map(URL url) {    execute(() -> {        String registryCluster = getRegistryCluster(url);        // 獲取MetadataReport,也就是元數據中心的訪問路徑        MetadataReport metadataReport = MetadataReportInstance.getMetadataReport(registryCluster);        ......        int currentRetryTimes = 1;        boolean success;        String newConfigContent = getName();        do {            // 獲取元數據中心中存儲的應用的版本信息            ConfigItem configItem = metadataReport.getConfigItem(serviceInterface, DEFAULT_MAPPING_GROUP);            String oldConfigContent = configItem.getContent();            if (StringUtils.isNotEmpty(oldConfigContent)) {                boolean contains = StringUtils.isContains(oldConfigContent, getName());                if (contains) {                    break;                }                newConfigContent = oldConfigContent + COMMA_SEPARATOR + getName();            }            // 在元數據中心創(chuàng)建mapping節(jié)點,并將暴露的服務數據存到元數據中心,這里的元數據中心用zookeeper實現的            // 目錄:/dubbo/mapping/xxxService            // 數據:configItem.content為${application-name},configItem.ticket為版本好            success = metadataReport.registerServiceAppMapping(serviceInterface, DEFAULT_MAPPING_GROUP, newConfigContent, configItem.getTicket());        } while (!success && currentRetryTimes++ <= CAS_RETRY_TIMES);    });} 

3、ZookeeperMetadataReport#registerServiceAppMapping

  
 
 
 
  1. public boolean registerServiceAppMapping(String key, String group, String content, Object ticket) {    try {        if (ticket != null && !(ticket instanceof Stat)) {            throw new IllegalArgumentException("zookeeper publishConfigCas requires stat type ticket");        }        String pathKey = buildPathKey(group, key);        // 1. 創(chuàng)建/dubbo/mapping/xxxService目錄,存儲的數據為configItem        // 2. 生成版本號        zkClient.createOrUpdate(pathKey, content, false, ticket == null ? 0 : ((Stat) ticket).getVersion());        return true;    } catch (Exception e) {        logger.warn("zookeeper publishConfigCas failed.", e);        return false;    }} 

到這里,暴露Triple協(xié)議的服務并注冊 registry 協(xié)議的流程就結束了。主要是將以前接口級服務發(fā)現機制中注冊到注冊中心中的數據(應用實例數據+服務接口數據)拆分出來了。注冊 registry 協(xié)議部分將應用實例數據注冊到注冊中心,在 Exporter 暴露完以后通過調用 MetadataUtils#publishServiceDefinition 將服務接口數據注冊到元數據中心。

4、暴露MetadataService服務

MetadataService 主要是對 Consumer 側提供一個可以獲取元數據的 API,暴露流程是復用了 Triple 協(xié)議的服務暴露流程

整體時序圖

暴露 MetadataService 的入口

核心代碼在 DubboBootstrap#exportMetadataService 中,具體如下:

  
 
 
 
  1. private void exportMetadataService() {    // 暴露MetadataServer    metadataServiceExporter.export();} 

暴露 MetadataService

核心代碼在 ConfigurableMetadataServiceExporter#export 中,具體如下:

  
 
 
 
  1. public ConfigurableMetadataServiceExporter export() {    if (!isExported()) {        // 定義MetadataService的ServiceConfig        ServiceConfig serviceConfig = new ServiceConfig<>();        serviceConfig.setApplication(getApplicationConfig());        // 不會注冊到注冊中心        serviceConfig.setRegistry(new RegistryConfig("N/A"));        serviceConfig.setProtocol(generateMetadataProtocol());        serviceConfig.setInterface(MetadataService.class);        serviceConfig.setDelay(0);        serviceConfig.setRef(metadataService);        serviceConfig.setGroup(getApplicationConfig().getName());        serviceConfig.setVersion(metadataService.version());        serviceConfig.setMethods(generateMethodConfig());        // 用暴露Triple協(xié)議服務的流程來暴露MetadataService        // 采用的是Dubbo協(xié)議        serviceConfig.export();        this.serviceConfig = serviceConfig;    }    return this;} 

由于暴露 MetadataService 的流程是復用前面提到的暴露 Triple 協(xié)議服務的流程,整個過程有少許地方會不同,這些不同之處在上面的代碼中都已經標明,所以就不再贅述了。

注冊 ServiceInstance 實例

注冊 ServiceInstance 的目的是為了定時更新 Metadata,當有更新的時候就會通過 MetadataReport 來更新版本號讓 Consumer 端感知到。

核心代碼在 DubboBootstrap#registerServiceInstance 和 DubboBootstrap#doRegisterServiceInstance 中,具體如下:

  
 
 
 
  1. private void registerServiceInstance() {    ....    // 創(chuàng)建ServiceInstance    // ServiceInstance中包含以下字段    // 1. serviceName:${application-name}    // 2. host: 192.168.31.167    // 3. port: 2080    // 4. metadata: 服務接口級相關的數據,比如:methods等數據    // 同時,還會對ServiceInstance數據中的字段進行補充,分別調用下面4個ServiceInstanceCustomizer實例    // 1)ServiceInstanceMetadataCustomizer    // 2)MetadataServiceURLParamsMetadataCustomizer    // 3)ProtocolPortsMetadataCustomizer    // 4)ServiceInstanceHostPortCustomizer    ServiceInstance serviceInstance = createServiceInstance(serviceName);    boolean registered = true;    try {        // 注冊ServiceInstance        doRegisterServiceInstance(serviceInstance);    } catch (Exception e) {        registered = false;        logger.error("Register instance error", e);    }    // 如果注冊成功,定時更新Metadata,沒10s更新一次    if(registered){        executorRepository.nextScheduledExecutor().scheduleAtFixedRate(() -> {            ......            try {                // 刷新Metadata和ServiceInstance                ServiceInstanceMetadataUtils.refreshMetadataAndInstance(serviceInstance);            } catch (Exception e) {                ......            } finally {                ......            }        }, 0, ConfigurationUtils.get(METADATA_PUBLISH_DELAY_KEY, DEFAULT_METADATA_PUBLISH_DELAY), TimeUnit.MILLISECONDS);    }} 

DubboBootstrap#doRegisterServiceInstance

  
 
 
 
  1. private void doRegisterServiceInstance(ServiceInstance serviceInstance) {    if (serviceInstance.getPort() > 0) {        // 發(fā)布Metadata數據到遠端存儲元數據中心        // 調用RemoteMetadataServiceImpl#publishMetadata,        // 內部會調用metadataReport#publishAppMetadata        publishMetadataToRemote(serviceInstance);        logger.info("Start registering instance address to registry.");        getServiceDiscoveries().forEach(serviceDiscovery ->{            ServiceInstance serviceInstanceForRegistry = new DefaultServiceInstance((DefaultServiceInstance) serviceInstance);            calInstanceRevision(serviceDiscovery, serviceInstanceForRegistry);          
    網頁題目:深入解析Dubbo3.0服務端暴露全流程
    文章源于:http://www.5511xx.com/article/cciijeg.html