国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

深入解析 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

2012-03-02 10:38:33

MySQL

2013-03-25 10:08:44

PHPWeb

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架構服務

2009-08-21 15:22:56

端口偵聽

2010-03-19 18:17:17

Java Server

2010-02-24 15:42:03

WCF服務端安全
點贊
收藏

51CTO技術棧公眾號

免费看毛片的网址| 亚洲一二三四区不卡| 2022中文字幕| 久久精品视频一区二区| 99在线免费观看| 亚洲成人7777| 激情aⅴ欧美一区二区欲海潮| 亚洲一二区在线| 成人做爰66片免费看网站| 婷婷免费在线视频| 欧美精品一卡二卡| 捆绑调教日本一区二区三区| 性金发美女69hd大尺寸| 欧美二区不卡| 久艹在线免费观看| 亚洲一级二级三级在线免费观看| 男人天堂亚洲| 青青草影院在线观看| 自拍偷拍免费精品| 97高清视频| 色网站国产精品| 小视频免费在线观看| 国产www精品| 精品一区二区三区视频| 深夜爽爽视频| 亚洲色图35p| 国产a级一级片| 欧美日本精品在线| 黄色网址入口| 一区二区三区四区五区视频在线观看| 一区 二区 三区| 日韩中文在线不卡| 亚洲黄色av| 亚洲黄色小视频在线观看| 日韩一区二区视频| 日韩丝袜视频| 大地资源第二页在线观看高清版| 91亚洲午夜精品久久久久久| 天天操天天爱天天爽| 免费观看成人高潮| 日本亚洲欧美天堂免费| 国产成人精品午夜| 成人丝袜高跟foot| 蜜芽在线免费观看| 青青久久av北条麻妃海外网| 国产精品1区2区3区| 久久视频www| 国产精品影片在线观看| 亚洲女优视频| 91在线视频精品| 三妻四妾的电影电视剧在线观看| 91国产美女在线观看| 国产最新精品精品你懂的| 国产精品二线| 国产精品一久久香蕉国产线看观看| 本田岬高潮一区二区三区| 91香蕉在线观看| 国产精品女人久久久久久| 国产.欧美.日韩| 337p粉嫩大胆噜噜噜噜噜91av| 久久久久一区二区三区| 日韩一级免费在线观看| 一级视频在线免费观看| 精品精品99| 久久亚洲免费视频| 97视频在线观看免费| 日本黄在线观看| 日韩制服丝袜先锋影音| 欧美激情精品久久久久久蜜臀| 自拍一区在线观看| 一区二区在线观看免费视频播放| 亚洲一级片在线看| 各处沟厕大尺度偷拍女厕嘘嘘| 香蕉成人在线| 久久精品视频一区| 韩剧1988免费观看全集| 在线视频色在线| 美女一区二区三区| 深夜福利国产精品| 亚洲成熟丰满熟妇高潮xxxxx| 日韩中文字幕区一区有砖一区 | 国产在线精品一区二区夜色 | 国产伦一区二区三区| 欧美精品在线免费观看| 亚洲精品自拍| 久久久久久久久久亚洲| 成人性生交大片免费看中文视频| 国内精品视频在线| 精品视频国产| 91免费综合在线| 久久超碰97人人做人人爱| 色中色综合成人| 我爱我色成人网| 免费网站在线观看视频| 精品国产伦一区二区三区观看方式 | 中文字幕理伦片免费看| 一区二区三区免费| 18国产精品| 国产视频一区二区不卡| 麻豆免费看一区二区三区| 午夜视频免费在线观看| 欧美美女激情18p| 伊人久久大香线蕉综合四虎小说 | 在线看三级网站视频| 亚洲国产日韩一级| 视频在线亚洲| 日本一区二区三区视频在线播放 | 日本免费成人网| 精品福利二区三区| 亚洲综合色网| 伊人久久大香线蕉午夜av| 亚洲高清不卡在线观看| 黄色小说在线播放| 亚洲一区免费看| 粉嫩老牛aⅴ一区二区三区| 亚洲精品三区| 国产精品jizz在线观看麻豆| 九色|91porny| 原千岁中文字幕| 国产成人小视频在线观看| 国产精品国产三级国产a | 一区二区精彩视频| 91久久综合亚洲鲁鲁五月天| 婷婷成人综合网| 欧美高清视频看片在线观看| 成年人免费网站| 日韩一区二区精品视频| 国产伦精品一区二区三区免费| 欧美日韩在线精品一区二区三区激情综合 | 国产69精品久久久久按摩| 国产aaa一级片| 国产亚洲欧美视频| 99久久久国产精品| 成人免费毛片嘿嘿连载视频…| 精品久久久久久一区二区里番| 91精品在线观看入口| 麻豆精品在线播放| 91免费在线| 国产www免费| 久久久国产影院| 精品久久久久久久中文字幕| 久久99久久99小草精品免视看| 天天躁日日躁狠狠躁欧美巨大小说 | 色综合久综合久久综合久鬼88| 91国在线观看| 99re热视频这里只精品| 国产污视频在线播放| 免费高清一区二区三区| 国产成人精品免费久久久久| 欧美α欧美αv大片| 国产一区日韩二区欧美三区| 第一福利在线视频| 天天摸天天碰天天添| 日韩av电影免费观看| 91精品视频网| 亚洲人亚洲人成电影网站色| 国产在线精品免费av| 欧美午夜一区二区福利视频| 国产96在线亚洲| 成人va天堂| 在线免费黄色| 美女在线视频一区二区| 成人午夜小视频| 欧美午夜不卡在线观看免费| 六月婷婷一区| 偷拍亚洲色图| 成年人视频免费在线观看| 极品美女扒开粉嫩小泬| 国内精品400部情侣激情| 婷婷夜色潮精品综合在线| 肉肉av福利一精品导航| 老司机深夜福利在线观看| 中文字幕日韩精品无码内射| 综合久久五月天| 一区二区三区四区在线播放| 国产一区导航| 精品美女在线观看视频在线观看| 久久久久无码国产精品一区| 日韩三级成人av网| 中文字幕第一区二区| 一区二区三区日本久久久| 色播五月综合网| 国产噜噜噜噜噜久久久久久久久 | 999精品视频在线| 激情视频一区二区| 久久成人18免费网站| 午夜精品影院在线观看| 久久一区视频| 欧美成人高清视频在线观看| 国产精品黄页网站在线播放免费| 国产精品美女网站| 欧美日韩在线直播| 丁香婷婷深情五月亚洲| 日韩a一区二区| аⅴ资源天堂资源库在线| 国产九九热视频| 午夜精品一区二区三区在线观看 | 日韩精品分区| 中文字幕精品—区二区日日骚| 亚洲人成欧美中文字幕|