关于dubbo:深入解析-Dubbo-30-服务端暴露全流程

简介: 随着云原生时代的到来,Dubbo 3.0 的一个很重要的指标就是全面拥抱云原生。正因如此,Dubbo 3.0 为了可能更好的适配云原生,将原来的接口级服务发现机制演进为利用级服务发现机制。

作者介绍

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

背景

随着云原生时代的到来,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 协定的服务为例,服务端裸露全流程时序图如下:

咱们能够看到,整个的裸露流程还是挺简单的,一共能够分为四个局部:

  • 裸露 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 协定的入口

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


整体时序图

通过 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 协定的服务裸露流程


整体时序图

裸露 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 云原生之路的同学能够积极参与社区奉献!

原文链接
本文为阿里云原创内容,未经容许不得转载。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理