精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

国产成人精品一区二区三区在线观看 | 中国精品18videos性欧美| 欧美另类z0zxhd电影| 一级一片免费播放| 成人免费公开视频| 国产日韩一区二区三区在线| 国产一区二区日韩| 成人性生交视频免费观看| 国内在线免费视频| 99久久精品国产一区| 国产精品视频yy9099| 欧美日韩国产精品一区二区三区| 亚洲影院天堂中文av色| 欧美日韩激情一区| 精品国产一区三区| 免费在线观看av| 国产福利不卡视频| 性欧美办公室18xxxxhd| 中文乱码字幕高清一区二区| 丁香综合av| 欧美日韩电影在线播放| av在线播放亚洲| 久草免费在线| 26uuu精品一区二区三区四区在线| 国产欧美中文字幕| 久久久久久国产精品免费播放| 在线视频亚洲专区| 日韩视频永久免费| 美女在线视频一区二区| 涩涩涩视频在线观看| 亚洲人成在线播放网站岛国| 欧美日韩在线观看一区| 国精产品乱码一区一区三区四区| 久久精品国产一区二区| 57pao国产成人免费| 欧美激情精品久久| 色婷婷综合网| 国产性色av一区二区| 天天躁日日躁狠狠躁av麻豆男男 | 97最新国自产拍视频在线完整在线看| 福利一区二区在线观看| 国产日韩在线一区| 久操视频在线免费观看| 18成人免费观看视频| 久久天天躁狠狠躁夜夜躁| 99久久精品免费视频| 欧美偷窥清纯综合图区| 亚洲高清不卡av| av天堂一区二区| 老司机亚洲精品一区二区| 欧美日韩一区二区在线视频| 99视频免费播放| 裤袜国产欧美精品一区| 欧美日韩国产精品一区| 免费视频爱爱太爽了| 日韩精品分区| 亚洲影院理伦片| 狠狠干视频网站| 成人video亚洲精品| 亚洲婷婷在线视频| 一区二区三区三区在线| 在线观看免费高清完整| 日本一区二区三区四区在线视频 | 日韩一区电影| 中文字幕成人精品久久不卡| 舐め犯し波多野结衣在线观看| 蜜桃成人av| 国产亚洲一区精品| 东方伊人免费在线观看| 手机亚洲手机国产手机日韩| 中文字幕久久亚洲| 五月婷婷综合激情网| 午夜国产一区二区| 欧美精品在线视频观看| 国产精品99精品| 日韩午夜av在线| 日本一区二区三区在线播放| 男人天堂视频在线| 美腿丝袜亚洲色图| 亚洲va码欧洲m码| 亚洲精品国产精品乱码不卡| 99在线热播精品免费| 久久综合九色99| 国产三级在线免费| 国产精品色在线观看| 亚洲最新免费视频| 操喷在线视频| 在线观看欧美黄色| 免费黄频在线观看| 999国产精品一区| 亚洲精品美女久久久| 日韩精品电影一区二区| 99精品在线| 国内免费精品永久在线视频| 日韩欧美成人一区二区三区| 免费观看在线综合色| 亚洲一区二区自拍| 无码国产精品高潮久久99| 久久精品亚洲乱码伦伦中文| 国产日韩视频在线播放| 2001个疯子在线观看| 日本丰满少妇一区二区三区| 交换做爰国语对白| 制服丝袜日韩| 欧美成人网在线| 日韩免费av网站| 国产一区二区导航在线播放| 久久久久一区二区三区| 日本成人在线播放| 精品露脸国产偷人在视频| 亚洲欧洲日本精品| 欧美人成在线观看ccc36| 日韩中文字幕久久| 日韩精品在线不卡| 九一久久久久久| 精品午夜一区二区三区| 黄视频网站在线| 色综合久久久久| 中国男女全黄大片| 色乱码一区二区三区网站| 91国内在线视频| 国产日本精品视频| 久久精品一区二区三区不卡| 91.com在线| 四虎视频在线精品免费网址| 亚洲欧美另类人妖| 国产一级中文字幕| 国精产品一区一区三区mba桃花 | 污视频网址在线观看| 日韩欧美在线精品| 久久久亚洲国产| 97超碰中文字幕| 国产免费观看久久| 国产精品动漫网站| 欧美久久精品| 午夜精品福利电影| 午夜老司机福利| 国产精品传媒视频| 中文字幕第17页| 国产99亚洲| 欧美一二三视频| 韩国中文字幕hd久久精品| 亚洲男人天堂一区| 岛国毛片在线播放| 日韩欧美字幕| 国产精品午夜国产小视频| 加勒比一区二区三区在线| 午夜久久电影网| 午夜男人的天堂| 黄色欧美日韩| 国产午夜精品在线| 九色porny视频在线观看| 精品福利二区三区| 欧美日韩中文视频| 成人精品国产福利| 国产精品12345| 国产精品中文字幕制服诱惑| 欧美激情视频三区| 亚洲精品久久久蜜桃动漫 | 国产www在线| 久久免费的精品国产v∧| 欧美性大战久久久久xxx| 青青草久久爱| 日本精品视频在线观看| 久久视频www| 欧美三级电影网| 你懂得在线观看| 国产麻豆精品95视频| 中文字幕日韩精品无码内射| 国产ts一区| 欧美一级电影在线| jyzzz在线观看视频| 欧美日韩久久一区二区| 在线观看黄网址| 国产91高潮流白浆在线麻豆| 777777av| 欧美一二区在线观看| 国产精品揄拍一区二区| mm1313亚洲国产精品美女| 欧美videofree性高清杂交| 日韩av电影网| 国产日韩影视精品| 久久精品一卡二卡| av成人天堂| 五月天综合网| 日韩中文字幕一区二区高清99| 性日韩欧美在线视频| 黄色在线视频观看网站| 6080国产精品一区二区| 日韩精品一区二区不卡| 欧美国产日本韩| 中文字幕视频观看| 日韩电影免费一区| 欧美极品少妇无套实战| 你微笑时很美电视剧整集高清不卡| 国产精品视频自拍| 国产www视频在线观看| 亚洲色图美腿丝袜| 国产chinasex对白videos麻豆| 污片在线观看一区二区| 久久精品日韩无码| 99国产欧美久久久精品| 手机版av在线| 国产精品日本| 在线视频一二三区| 自拍偷拍一区| 91九色对白| 精品视频在线一区二区在线| 欧美大片在线看免费观看| 国产精品一区二区婷婷| 精品久久人人做人人爰| 姑娘第5集在线观看免费好剧| 一区二区国产视频| 国产福利在线导航| 91麻豆swag| 国产成人精品综合久久久久99 | 亚洲精品视频在线看| 一区二区不卡免费视频| 国产麻豆精品视频| 亚洲免费一级视频| 亚洲一卡久久| h无码动漫在线观看| 国产精品麻豆久久| 日本一区美女| 亚州av一区| 国产伦精品一区二区三区高清 | 宅男噜噜噜66国产日韩在线观看| 亚洲成人网上| 美日韩中文字幕| 国产伦精品一区二区三区视频孕妇| 日韩免费在线电影| 国产精品久久久久aaaa九色| 无遮挡在线观看| 久久久久久伊人| 宅男在线观看免费高清网站 | 青草视频在线免费直播| 中文字幕亚洲第一| 国产福利免费在线观看| 亚洲精品日韩在线| 亚洲 美腿 欧美 偷拍| 日韩美女视频在线| 国产特级黄色片| 8v天堂国产在线一区二区| 中文字幕一区二区三区人妻四季| 欧美日韩中文在线观看| 狠狠躁夜夜躁人人爽天天高潮| 亚洲制服丝袜av| 欧美日韩一级大片| 亚洲精品日产精品乱码不卡| 免费在线观看h片| 亚洲日本在线天堂| 午夜国产福利一区二区| 亚洲欧美福利一区二区| 国产成人自拍网站| 亚洲女同一区二区| 五月婷婷一区二区| 亚洲成人综合网站| 欧美一区二区激情视频| 欧美日韩在线视频首页| 欧美一二三区视频| 色婷婷亚洲婷婷| 国产午夜无码视频在线观看| 欧美性色欧美a在线播放| 在线观看中文字幕码| 777色狠狠一区二区三区| 国产成人麻豆精品午夜在线| 精品国产青草久久久久福利| 亚洲 欧美 自拍偷拍| 国产香蕉精品视频一区二区三区 | 日韩在线麻豆| 欧日韩一区二区三区| 久久综合成人| 国产一二三区在线播放| 亚洲人成人一区二区三区| 精品一卡二卡三卡| 国产一区在线精品| 在线中文字日产幕| 久久久久亚洲综合| 欧美人禽zoz0强交| 亚洲成在线观看| 这里只有久久精品视频| 666欧美在线视频| 神宫寺奈绪一区二区三区| 尤物yw午夜国产精品视频明星| 成人毛片av在线| 欧美最猛性xxxxx(亚洲精品)| 久久福利在线| 国产精品对白一区二区三区| 国产一区二区三区四区五区| 99亚洲国产精品| 国产精品外国| 欧美美女一级片| 成人av网在线| 国产三级精品三级观看| 亚洲成人激情综合网| 中文字幕乱码人妻二区三区| 欧美tickling网站挠脚心| 日本一卡二卡四卡精品| 亚洲欧美日韩在线一区| 成人激情电影在线看| 欧美美女18p| 99热99re6国产在线播放| 国产一区二区视频在线观看| 日韩高清一区| 亚洲成人第一| 亚洲久久在线| 天天视频天天爽| 91视频在线看| 日本 欧美 国产| 香蕉加勒比综合久久| 91麻豆成人精品国产| 亚洲国模精品一区| 成人三级网址| 日本一欧美一欧美一亚洲视频| 2020国产精品小视频| 极品尤物一区二区三区| 久久在线视频免费观看| 88av.com| 粉嫩av亚洲一区二区图片| 无码一区二区三区在线| 激情久久av一区av二区av三区| 中文在线观看av| 精品国产3级a| 人人澡人人添人人爽一区二区| 国产精品 欧美在线| 少妇精品导航| 成年人深夜视频| 蜜桃av一区二区三区| 国产成人精品无码免费看夜聊软件| 亚洲男人的天堂在线aⅴ视频 | 欧美一区二区高清| 精品美女视频在线观看免费软件 | 亚洲一区 欧美| 天天操天天色综合| www.97av.com| 久久精品在线播放| 一级欧美视频| 日韩理论片在线观看| 日韩成人av影视| 你懂的在线观看网站| 一区二区三区四区精品在线视频| 国产精品高潮呻吟久久久| 亚洲社区在线观看| 欧美理论影院| 免费日韩av电影| 亚洲精品女人| av在线网站观看| 亚洲成a人在线观看| 天天操天天干天天插| 欧美日本啪啪无遮挡网站| 国产成人视屏| 超碰人人爱人人| 国内精品伊人久久久久av一坑| 卡通动漫亚洲综合| 69av一区二区三区| 波多野结衣在线网站| 5252色成人免费视频| 8848成人影院| 欧美日韩中文字幕在线播放| 久久99精品久久久久久| 亚洲黄色免费视频| 欧美日韩在线三级| 午夜毛片在线| 国产精品美女av| 日韩在线欧美| 久久人人爽av| 亚洲午夜日本在线观看| 成人久久精品人妻一区二区三区| 久久久久久91| 婷婷亚洲成人| 中文字幕乱码人妻综合二区三区| 国产午夜精品一区二区三区视频 | 日本一区二区视频在线播放| 在线亚洲高清视频| 深夜福利在线看| 国产成人免费91av在线| 国产成人调教视频在线观看 | 女尊高h男高潮呻吟| 在线中文字幕一区| 午夜视频在线免费观看| 99在线看视频| 亚洲伊人网站| 久久久男人的天堂| 一本色道久久综合狠狠躁的推荐 | 久久综合久久88| 蜜桃精品视频| 可以在线看的av网站| 久久久久久久久久久99999| 日本黄色中文字幕| 免费99精品国产自在在线| 丁香五月缴情综合网| 国产aaaaa毛片| 亚洲欧美综合另类在线卡通| 乱精品一区字幕二区| 日韩美女视频在线观看| 久久电影院7| 污污污www精品国产网站| 欧洲国产伦久久久久久久| 欧美人体视频xxxxx| 开心色怡人综合网站|