乐趣区

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

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

退出移动版