精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

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

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

背景

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

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

單機常駐內存下降 75%

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

什么是應用級服務發現

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

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

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

  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}}] 

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

  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}}] 

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

服務端暴露全流程

引入應用級服務發現機制以后,Dubbo 3.0 服務端暴露全流程和之前有很大的區別。暴露服務端全流程的核心代碼在 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 協議的服務為例,服務端暴露全流程時序圖如下:

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

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

1、暴露 injvm 協議的服務

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

整體時序圖

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

2、注冊 service-discovery-registry 協議

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

整體時序圖

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

注冊 service-discovery-registry 協議的入口

  1. private URL exportRemote(URL url, List<URL> registryURLs) {    if (CollectionUtils.isNotEmpty(registryURLs)) {        // 如果是多個注冊中心,通過循環對每個注冊中心進行注冊        for (URL registryURL : registryURLs) {            // 判斷是否是service-discovery-registry協議            // 將service-name-mapping參數的值設置為true            if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) {                url = url.addParameterIfAbsent(SERVICE_NAME_MAPPING_KEY, "true");            }            ......            // 注冊service-discovery-registry協議復用服務暴露流程            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 <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {    // 此時的protocol為RegistryProtocol類型    if (UrlUtils.isRegistry(invoker.getUrl())) {        return protocol.export(invoker);    }    ......} 

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

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

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

暴露 Triple 協議的服務

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

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

注冊service-discovery-registry協議

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

1、ServiceDiscoveryRegistry#register

  1. public final void register(URL url) {    // 只有服務端(Provider)才需要注冊    if (!shouldRegister(url)) {        return;    }    // 注冊service-discovery-registry協議    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協議的服務中接口相關的數據生成ServiceInfo            // 將ServiceInfo注冊到MetadataInfo中            metadataInfo.addService(new ServiceInfo(url));        }        metadataSemaphore.release();        return addURL(exportedServiceURLs, url);    } finally {        updateLock.readLock().unlock();    }} 

發布 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協議后發布onRegister事件                        listener.onRegister(url, registry);                    } catch (RuntimeException t) {                        logger.error(t.getMessage(), t);                        exception = t;                    }                }            }            if (exception != null) {                throw exception;            }        }    }} 

發布服務注冊事件

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

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

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

3、暴露Triple協議服務并注冊registry協議

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

暴露 Triple 協議的服務

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

整體時序圖

通過 InterfaceCompatibleRegistryProtocol 將 Invoker 轉化成 Exporter

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

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

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

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

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

注冊 registry 協議

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

1、FailbackRegistry#register

  1. public void register(URL url) {    if (!acceptable(url)) {        ......        try {            // 注冊registry協議            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<NotifyListener, ChildListener> 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);                }                // 創建臨時節點用來存儲configurators數據                // 目錄:/dubbo/xxxService/configurators                // 數據:應用的配置信息,可以在dubbo-admin中進行修改,默認為空                zkClient.create(path, false);                // 添加監聽器,用來監聽configurators中的變化                List<String> children = zkClient.addChildListener(path, zkListener);                if (children != null) {                    urls.addAll(toUrlsWithEmpty(url, path, children));                }            }            ......        }    } catch (Throwable e) {        ......    } 

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

1、ServiceConfig#exportUrl

  1. private void exportUrl(URL url, List<URL> registryURLs) {    ......    if (!SCOPE_NONE.equalsIgnoreCase(scope)) {        ......        if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {            url = exportRemote(url, registryURLs);            // 發布事件,更新服務接口相關的數據            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)) {        // 發布服務端(Provider)的服務接口信息到元數據中心        publishProvider(url);    } else {        ......    }}RemoteMetadataServiceImpl#publishProviderprivate void publishProvider(URL providerUrl) throws RpcException {    ......    try {        String interfaceName = providerUrl.getServiceInterface();        if (StringUtils.isNotEmpty(interfaceName)) {            ......            for (Map.Entry<String, MetadataReport> 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}節點下        doStoreProviderMetadata(providerMetadataIdentifier, data);        // 存儲到本地文件        // 路徑:xxxService:::provider:${application-name}         saveProperties(providerMetadataIdentifier, data, true, !syncReport);    } catch (Exception e) {        ......    }} 

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

1、ServiceConfig#exported

  1. protected void exported() {    exported = true;    List<URL> 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();            }            // 在元數據中心創建mapping節點,并將暴露的服務數據存到元數據中心,這里的元數據中心用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. 創建/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協議的服務并注冊 registry 協議的流程就結束了。主要是將以前接口級服務發現機制中注冊到注冊中心中的數據(應用實例數據+服務接口數據)拆分出來了。注冊 registry 協議部分將應用實例數據注冊到注冊中心,在 Exporter 暴露完以后通過調用 MetadataUtils#publishServiceDefinition 將服務接口數據注冊到元數據中心。

4、暴露MetadataService服務

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

整體時序圖

暴露 MetadataService 的入口

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

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

暴露 MetadataService

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

  1. public ConfigurableMetadataServiceExporter export() {    if (!isExported()) {        // 定義MetadataService的ServiceConfig        ServiceConfig<MetadataService> 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協議服務的流程來暴露MetadataService        // 采用的是Dubbo協議        serviceConfig.export();        this.serviceConfig = serviceConfig;    }    return this;} 

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

注冊 ServiceInstance 實例

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

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

  1. private void registerServiceInstance() {    ....    // 創建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) {        // 發布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);            ......            // 調用ZookeeperServiceDiscovery#doRegister注冊serviceInstance實例            // 將應用服務信息注冊到注冊中心中            // 目錄:/services/${application-name}/192.168.31.167:20800            // 數據:serviceInstance序列化后的byte數組            serviceDiscovery.register(serviceInstanceForRegistry);        });    }} 

通過上面的分析,我們可以很容易知道

ServiceInstance 是中包含 Metadata
Metadata 是存儲在 InMemoryWritableMetadataService 中的元數據,占用的是本地內存空間
InMemoryWritableMetadataService 用來更新 Metadata
ServiceInstance 是存儲在遠端元數據注冊中心中的數據結構
RemoteMetadataServiceImpl 會調用 metadataReport 將 ServiceInstance 數據更新到遠端元數據注冊中心

總結

通過對 Dubbo 3.0 服務端暴露全流程的解析可以看出,盡管應用級服務發現機制的實現要復雜很多,但是 Dubbo 3.0 為了能夠讓使用者平滑遷移,兼容了 2.7.x 的版本,所以在設計的時候很多地方都盡可能復用之前的流程。

從最近 Dubbo 3.0 發布的 Benchmark 數據來看,Dubbo 3.0 的性能和資源利用上確實提升了不少。Dubbo 3.0 在擁抱云原生的道路上還有很長的一段路要走,社區正在對 Dubbo 3.0 中核心流程進行梳理和優化,后續計劃支持多實例應用部署,希望有興趣見證 Dubbo 云原生之路的同學可以積極參與社區貢獻!

責任編輯:梁菲 來源: 阿里云云棲號
相關推薦

2010-08-03 09:59:30

NFS服務

2025-05-28 08:35:00

Nacos服務訂閱流程開發

2025-06-03 08:25:00

Nacos開發服務

2023-08-14 08:17:13

Kafka服務端

2021-04-16 08:54:03

CMS系統redisnode服務器

2021-06-11 06:54:34

Dubbo客戶端服務端

2016-03-18 09:04:42

swift服務端

2021-07-11 06:43:29

服務端Node路由

2011-01-13 13:48:52

Android 3.0

2013-03-25 10:08:44

PHPWeb

2012-03-02 10:38:33

MySQL

2017-02-08 08:46:39

瀏覽器服務端亂碼

2022-03-15 18:33:34

URL重構Dubbo3.0

2021-08-09 10:21:42

云原生Dubbo3.0 服務治理

2016-11-03 09:59:38

kotlinjavaspring

2021-05-25 08:20:37

編程技能開發

2021-08-28 09:06:11

Dubbo架構服務

2010-03-18 18:09:36

Java Socket

2021-04-26 13:20:06

Vue服務端渲染前端

2023-08-08 08:17:23

VasDolly服務端參數
點贊
收藏

51CTO技術棧公眾號

精品久久久久一区二区三区| 欧美视频一区二区在线| 多野结衣av一区| 久久久久久日产精品| 日韩美女激情视频| 成人免费精品动漫网站| 精品久久对白| 精品视频一区二区三区免费| 国产成人一区二区三区别| 婷婷国产在线| 国产一区二区视频在线| 午夜精品在线视频| 精品无码一区二区三区蜜臀| 欧美自拍一区| 7777精品伊人久久久大香线蕉最新版 | 久久精品国产久精国产一老狼| 日韩大尺度视频| 日韩国产激情| 一区二区激情视频| 亚洲欧洲精品一区二区| 五月婷婷综合久久| 国产剧情在线观看一区二区 | 国产三级一区| 精品成人国产在线观看男人呻吟| 国产高清精品软男同| 青青草免费在线视频| 高清av一区二区| 国产精品丝袜高跟| 国产精品熟女视频| 先锋影音久久| 久久免费视频在线| 丰满少妇被猛烈进入一区二区| 最新国产一区| 亚洲精品在线三区| 亚洲少妇一区二区| 免费一级欧美片在线观看网站| 一本到高清视频免费精品| www.亚洲成人网| av免费看在线| 日韩理论片网站| 亚洲欧美99| 国产1区2区3区在线| 91色.com| 久久一区二区三区av| 东京干手机福利视频| 国产精品一区二区在线观看网站| 国产综合视频在线观看| 国产一级片一区二区| 久久综合九色| 欧美在线视频观看| www毛片com| 久久精品一区二区三区中文字幕| 午夜免费在线观看精品视频| 国产一级片免费观看| 国产一区二区三区自拍| 久久免费在线观看| 日韩乱码人妻无码中文字幕| 在线日本高清免费不卡| 午夜精品视频在线| 好看的av在线| 久久一二三四| 国产精品欧美一区二区三区奶水| 亚洲中文无码av在线| 精品一区免费av| 亚洲一区久久久| 亚洲第一第二区| 99热这里都是精品| 日韩福利视频| 麻豆传媒视频在线观看免费| 中文字幕五月欧美| 成人免费a级片| 美女网站在线看| 欧美在线一区二区三区| 亚洲一区二区福利视频| 国产精品毛片av| 亚洲精品在线网站| 久久精品视频18| 围产精品久久久久久久| 欧美激情欧美激情| 国产成人在线免费视频| 日韩av一区二区三区四区| 国产精品免费一区二区三区都可以| 一级特黄aaa大片| 国产成人免费高清| 欧美一区二区高清在线观看| 欧美性天天影视| 亚洲午夜激情av| 91n.com在线观看| 榴莲视频成人app| 亚洲精品网址在线观看| 国产精品免费无码| 伊人久久大香线| 51精品在线观看| 97超碰国产在线| www.色综合.com| 亚洲国产欧洲综合997久久 | 国产精品黄色av| 精品人妻一区二区三区蜜桃 | 日韩高清三级| 欧美卡一卡二| 欧美性极品少妇| 精品人妻无码中文字幕18禁| 中国av一区| 欧美国产日本高清在线 | 国产免费一区| 日韩欧美小视频| 精品日本美女福利在线观看| 亚洲一级片网站| 欧美国产极品| 欧美精品日韩www.p站| 天天干天天色综合| 成人午夜视频在线观看| 亚洲一区三区在线观看| 少妇在线看www| 欧美xxxx在线观看| 三级黄色在线观看| 久久精品导航| 国产在线精品一区二区三区》 | www.精品久久| 国产精品久久久久久久久久久免费看| 97超碰人人澡| 免费欧美网站| 久久天天躁日日躁| 在线观看毛片视频| 国产日产亚洲精品系列| 黄色成人在线看| 亚洲高清在线一区| 久久精品91久久久久久再现| 精品一区二三区| 91视频国产资源| 黄色免费福利视频| 成人三级av在线| 欧美xxxx做受欧美| 国产色在线视频| 综合久久久久综合| 九九热99视频| 91一区在线| 国产精品自产拍在线观看中文| 毛片在线播放网站| 韩曰欧美视频免费观看| 久久久久久久人妻无码中文字幕爆| 影音先锋日韩在线| 成人中文字幕+乱码+中文字幕| av在线播放av| 欧美日韩精品系列| 俄罗斯毛片基地| 另类中文字幕网| 亚洲第一导航| 日韩欧美三区| 久久五月天综合| aaa国产视频| 亚洲精品成a人| 免费黄色在线播放| 日韩一区二区免费看| 极品日韩久久| 欧美极度另类| 中文字幕免费国产精品| 亚洲视频在线免费播放| 亚洲欧美福利一区二区| 色欲欲www成人网站| 亚洲经典视频在线观看| 国产区欧美区日韩区| 日本不卡网站| 在线看欧美日韩| 国产精品视频久久久久久| 亚洲精品网站在线观看| 日韩精品视频一区二区| 久久精品一区| 宅男av一区二区三区| 91精品啪在线观看国产爱臀| 欧美性视频网站| 在线观看免费网站黄| 欧美一区二区三区爱爱| 久久夜色精品亚洲| 亚洲国产精品av| av不卡中文字幕| 另类激情亚洲| 亚洲区成人777777精品| 欧美挤奶吃奶水xxxxx| 日韩免费观看高清| а√天堂资源地址在线下载| 精品福利一二区| 中文字幕69页| 亚洲精品菠萝久久久久久久| 成年人网站免费看| 国产一区二区按摩在线观看| 你懂的av在线| 91青青国产在线观看精品| 官网99热精品| 日韩av电影资源网| 欧美激情中文网| 粉嫩av在线播放| 精品久久国产97色综合| 97人妻一区二区精品视频| 亚洲精品一二三区| 欧美另类z0zx974| 成人免费看视频| 九色porny自拍| 亚洲经典在线看| gogogo免费高清日本写真| 日韩电影不卡一区| 成人中心免费视频| 婷婷激情一区| 高清亚洲成在人网站天堂| 在线观看国产原创自拍视频| 亚洲国产成人av在线| 国产人妻精品一区二区三区| 欧美性xxxx极品hd满灌| 欧美日韩三级在线观看| 欧美极品少妇xxxxⅹ高跟鞋 | 国产无人区一区二区三区| 在线观看欧美一区二区| 久久av资源网| 噼里啪啦国语在线观看免费版高清版| 国产精品啊啊啊| 吴梦梦av在线| av资源久久| 欧美国产二区| 久久香蕉精品香蕉| 波多野结衣一区二区三区在线观看| av在线不卡精品| 日本欧美国产在线| 欧美aa在线观看| 久久免费精品视频| 99在线视频观看| 精品国产欧美一区二区五十路| 国产在线三区| 亚洲美女黄色片| 五月婷婷激情在线| 精品成人佐山爱一区二区| 国产xxxx孕妇| 欧美日本韩国一区| 在线播放一级片| 欧美乱妇20p| 一级aaaa毛片| 欧美日韩一二区| 中文字幕欧美人妻精品| 在线观看国产日韩| 成人一二三四区| 欧美色视频在线| 最新在线中文字幕| 欧美亚洲愉拍一区二区| 国产成人自拍偷拍| 欧美视频日韩视频在线观看| 日本一本在线观看| 在线欧美日韩精品| 黄色污污视频软件| 欧美性感一类影片在线播放| 中文字幕观看视频| 这里只有精品电影| 国产浮力第一页| 精品国产一区二区三区久久久蜜月| 国产成人精品av在线观| 日韩三级视频在线观看| www.国产免费| 亚洲精品福利免费在线观看| 欧美日韩国产综合视频 | caopo在线| 欧美夫妻性视频| sm久久捆绑调教精品一区| 5566成人精品视频免费| 最新日韩精品| 国产精品永久在线| 美女久久精品| 好吊色欧美一区二区三区视频| 欧美人成在线观看ccc36| 久久综合入口| 色喇叭免费久久综合| 免费日韩在线观看| 99精品久久久| 欧美婷婷精品激情| 国产成人av电影在线| 黄色a一级视频| 国产精品色在线| 久久久精品91| 欧美日韩在线免费观看| 在线免费观看视频网站| 日韩欧美综合一区| 免费黄色片在线观看| 久久精品亚洲一区| aa视频在线观看| 国产日产亚洲精品| 福利片在线一区二区| 日本视频一区二区在线观看| 综合视频在线| 免费在线激情视频| 狠狠色狠狠色合久久伊人| 亚洲av无码一区二区三区观看| 国产女同性恋一区二区| 精品无码人妻一区二区三| 色欧美88888久久久久久影院| 91美女精品网站| 日韩精品中文字幕在线| 欧美极品另类| 欧美在线视频一区| 麻豆国产精品| 亚洲成色最大综合在线| 精品91在线| 在线一区二区不卡| 国产丝袜美腿一区二区三区| 久久久久香蕉视频| 欧美麻豆精品久久久久久| 水中色av综合| 欧美激情视频在线免费观看 欧美视频免费一 | 日本久久91av| 亚洲一区网址| 亚洲精品久久久久久一区二区| 在线日韩欧美| 日日夜夜精品视频免费观看| 久久久蜜桃精品| 日本少妇裸体做爰| 欧美一级片在线观看| 亚洲视频tv| 国产成人精品999| 欧美激情极品| 青青青在线视频播放| 国产专区综合网| 99自拍偷拍视频| 色中色一区二区| 天堂av在线7| 欧美极度另类性三渗透| 二区三区精品| 国产精品美女在线播放| 男女男精品视频| 国产熟妇久久777777| 天天综合天天做天天综合| 精品人妻伦一区二区三区久久| 中文字幕av一区中文字幕天堂 | 亚洲第一成人网站| 午夜成人免费电影| 亚洲精品一区二区三区不卡| 久久久国产91| 亚洲视频资源| 中国成人亚色综合网站| 免费人成精品欧美精品 | 不卡av一区二区| 日韩欧美在线免费观看视频| 91片在线免费观看| av大全在线观看| 精品中文字幕久久久久久| 欧亚av在线| 免费日韩av电影| 久热国产精品| 天天舔天天操天天干| 欧美在线观看你懂的| а天堂8中文最新版在线官网| 国产精品高潮粉嫩av| gogogo高清在线观看一区二区| 午夜欧美福利视频| 欧美韩国日本综合| 中文字幕日韩第一页| 在线日韩第一页| 亚洲精品aa| 99亚洲国产精品| 粉嫩av一区二区三区| 国产主播在线播放| 日韩精品视频在线观看免费| xx欧美视频| 亚洲精品一区二区三区樱花 | 黄色a一级视频| 在线观看视频一区二区| 嫩草香蕉在线91一二三区| 91免费电影网站| 亚洲性视频h| 三级网站在线免费观看| 在线观看av一区| 国产福利视频在线| 国产欧美日韩伦理| 久久久综合网| 熟女av一区二区| 日韩精品一区二区三区在线播放 | 亚洲精品免费在线视频| 亚洲性人人天天夜夜摸| 六月婷婷七月丁香| 91精品国产乱| 黄色在线观看www| 亚洲一区二区精品在线| 成人晚上爱看视频| 无码人妻精品一区二区三区9厂 | 在线亚洲美日韩| 波多野洁衣一区| 制服丝袜在线一区| 欧美精品福利在线| 国产一区2区| 久久精品无码一区二区三区毛片| 午夜久久久久久久久久一区二区| 成人在线免费视频| 97免费资源站| 日韩激情在线观看| 久久一区二区三| 国产一区二区三区三区在线观看| 欧美a在线观看| 免费观看成人网| 亚洲午夜久久久久久久久电影院 | ...av二区三区久久精品| 懂色av蜜臀av粉嫩av分享吧| 国产精品入口日韩视频大尺度| 黄色日韩在线| 天天操夜夜操av| 亚洲午夜未删减在线观看|