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

本文涉及的产品
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
性能测试 PTS,5000VUM额度
云原生网关 MSE Higress,422元/月
简介: 随着云原生时代的到来,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)分别设置了不同的超时时间。在接口级和应用级服务发现机制下,注册到注册中心的数据是截然不同的。如下图所示:


  • 接口级服务发现机制下注册中心中的数据


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


  • 应用级服务发现机制下注册中心中的数据


"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 中,具体如下:


private void doStart() {
    // 1. 暴露Dubbo服务
    exportServices();
    // If register consumer instance or has exported services
    if (isRegisterConsumerInstance() || hasExportedServices()) {
        // 2. 暴露元数据服务
        exportMetadataService();
        // 3. 定时更新和上报元数据
        registerServiceInstance();
        ....
    }
    ......
}


假设以 Zookeeper 作为注册中,对外暴露 Triple 协议的服务为例,服务端暴露全流程时序图如下:

image.png


我们可以看到,整个的暴露流程还是挺复杂的,一共可以分为四个部分:


  • 暴露 injvm 协议的服务
  • 注册 service-discovery-registry 协议
  • 暴露 Triple 协议的服务并注册 registry 协议
  • 暴露 MetadataService 服务


下面会分别从这四个部分对服务暴露全流程进行详细讲解。


1、暴露 injvm 协议的服务


injvm 协议的服务是暴露在本地的,主要原因是在一个应用上往往既有 Service(暴露服务)又有 Reference(服务引用)的情况存在,并且 Reference 引用的服务就是在该应用上暴露的 Service。为了支持这种使用场景,Dubbo 提供了 injvm 协议,将 Service 暴露在本地,Reference 就可以不需要走网络直接在本地调用 Service。

image.gifimage.png

整体时序图


由于这部分内容在之前的接口级服务发现机制中是类似的,所以相关的核心代码就不在这里展开讨论了。


2、注册 service-discovery-registry 协议


注册 service-discovery-registry 协议的核心目的是为了注册与服务相关的元数据,默认情况下元数据通过 InMemoryWritableMetadataService 将数据存储在本地内存和本地文件。

image.gifimage.png

整体时序图


核心代码在 ServiceConfig#exportRemote 中,具体如下:


  • 注册 service-discovery-registry 协议的入口


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 中,具体如下:


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 中,具体如下:


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 中,具体如下:


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 中,具体如下:


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


public final void register(URL url) {
    // 只有服务端(Provider)才需要注册
    if (!shouldRegister(url)) {
        return;
    }
    // 注册service-discovery-registry协议
    doRegister(url);
}


2、ServiceDiscoveryRegistry#doRegister


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 中,具体如下:


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 中,具体如下:


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 中,具体如下:


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 协议的过程仅仅注册了应用实例相关的信息,也就是之前提到的应用级服务发现机制。

image.gifimage.png

整体时序图


  • 通过 InterfaceCompatibleRegistryProtocol 将 Invoker 转化成 Exporter


核心代码在 ProtocolListenerWrapper#export 中,具体如下:


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 中,具体如下:


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


public void register(URL url) {
    if (!acceptable(url)) {
        ......
        try {
            // 注册registry协议
            doRegister(url);
        } catch (Exception e) {
            ......
        }
    }
}


2、ServiceDiscoveryRegistry#doRegister


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&timestamp=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


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


2、ZookeeperRegistry#doSubscribe


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


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


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


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


public void publishServiceDefinition(URL url) {
    checkRemoteConfigured();
    String side = url.getSide();
    if (PROVIDER_SIDE.equalsIgnoreCase(side)) {
        // 发布服务端(Provider)的服务接口信息到元数据中心
        publishProvider(url);
    } else {
        ......
    }
}
RemoteMetadataServiceImpl#publishProvider
private 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


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


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


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


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 协议的服务暴露流程

image.png

整体时序图


  • 暴露 MetadataService 的入口


核心代码在 DubboBootstrap#exportMetadataService 中,具体如下:


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

  • 暴露 MetadataService


核心代码在 ConfigurableMetadataServiceExporter#export 中,具体如下:


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 中,具体如下:


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


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 云原生之路的同学可以积极参与社区贡献!


作者


熊聘,Github账号pinxiong,Apache Dubbo贡献者,关注RPC、Service Mesh和云原生等领域。现任职于携程国际事业部研发团队,负责市场营销、云原生等相关工作。


相关文章
|
1月前
|
JavaScript 前端开发 开发者
Vue执行流程及渲染解析
【10月更文挑战第2天】
104 58
|
1月前
|
JavaScript 前端开发 UED
Vue执行流程及渲染解析
【10月更文挑战第5天】
|
1月前
|
存储 搜索推荐 数据库
运用LangChain赋能企业规章制度制定:深入解析Retrieval-Augmented Generation(RAG)技术如何革新内部管理文件起草流程,实现高效合规与个性化定制的完美结合——实战指南与代码示例全面呈现
【10月更文挑战第3天】构建公司规章制度时,需融合业务实际与管理理论,制定合规且促发展的规则体系。尤其在数字化转型背景下,利用LangChain框架中的RAG技术,可提升规章制定效率与质量。通过Chroma向量数据库存储规章制度文本,并使用OpenAI Embeddings处理文本向量化,将现有文档转换后插入数据库。基于此,构建RAG生成器,根据输入问题检索信息并生成规章制度草案,加快更新速度并确保内容准确,灵活应对法律与业务变化,提高管理效率。此方法结合了先进的人工智能技术,展现了未来规章制度制定的新方向。
34 3
|
1月前
|
存储 缓存 边缘计算
揭秘直播带货背后的黑科技:播放流程全解析!
大家好,我是小米,今天聊聊社区直播带货的技术细节。我们将探讨直播播放流程中的关键技术,包括 HTTP DASH 协议、POP(Point of Presence)缓存和一致性哈希算法等。通过这些技术,直播流能根据网络状况动态调整清晰度,保证流畅体验。POP 和 DC 的多层次缓存设计减少了延迟,提升了观看效果。无论是技术人员还是直播运营者,都能从中受益。希望通过本文,你能更好地理解直播背后的技术原理。
43 3
|
1月前
|
程序员 C++
C++编程:While与For循环的流程控制全解析
总结而言,`while`循环和 `for`循环各有千秋,它们在C++编程中扮演着重要的角色。选择哪一种循环结构应根据具体的应用场景、循环逻辑的复杂性以及个人的编程风格偏好来决定。理解这些循环结构的内在机制和它们之间的差异,对于编写高效、易于维护的代码至关重要。
51 1
|
30天前
|
敏捷开发 数据可视化 测试技术
解析软件项目管理:以板栗看板为例,其如何有效影响并优化软件开发流程
软件项目管理是一个复杂而重要的过程,涵盖了软件产品的创建、维护和优化。其核心目标是确保软件项目能够顺利完成,同时满足预定的质量、时间和预算目标。本文将深入探讨软件项目管理的内涵及其对软件开发过程的影响,并介绍一些有效的管理工具。
|
2月前
|
缓存 负载均衡 Dubbo
Dubbo技术深度解析及其在Java中的实战应用
Dubbo是一款由阿里巴巴开源的高性能、轻量级的Java分布式服务框架,它致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
72 6
|
2月前
|
监控 数据挖掘 BI
项目管理流程全解析及关键步骤介绍
项目管理流程是项目成功的基石,涵盖启动、规划、执行、监控和收尾等阶段。Zoho Projects 等软件可提高效率,支持结构化启动与规划、高效执行与协作及实时监控。这些流程和工具对项目的全局视角、团队协作和风险控制至关重要。项目管理软件适用于不同规模企业,实施时间因软件复杂度和企业准备而异。
78 2
|
1月前
|
JSON JavaScript API
商品详情数据接口解析返回的JSON数据(API接口整套流程)
商品详情数据接口解析返回的JSON数据是API接口使用中的一个重要环节,它涉及从发送请求到接收并处理响应的整个流程。以下是一个完整的API接口使用流程,包括如何解析返回的JSON数据:

推荐镜像

更多