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

简介: 随着云原生时代的到来,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 servicesif (isRegisterConsumerInstance() || hasExportedServices()) {// 2. 暴露元数据服务exportMetadataService();// 3. 定时更新和上报元数据registerServiceInstance();....}......
}

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

image.gif

image.png

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

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

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

1、暴露 injvm 协议的服务

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

image.png

整体时序图

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

2、注册 service-discovery-registry 协议

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

image.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参数的值设置为trueif (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包装成DelegateProviderMetaDataInvokerif (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.gif

image.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协议对应的是ZookeeperRegistryfinal 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=1629588251493zkClient.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#doSubscribedoSubscribe(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#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

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() {// 暴露MetadataServermetadataServiceExporter.export();
}
  • 暴露 MetadataService

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

public ConfigurableMetadataServiceExporter export() {if (!isExported()) {// 定义MetadataService的ServiceConfigServiceConfig<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)ServiceInstanceHostPortCustomizerServiceInstance serviceInstance = createServiceInstance(serviceName);boolean registered = true;try {// 注册ServiceInstancedoRegisterServiceInstance(serviceInstance);} catch (Exception e) {registered = false;logger.error("Register instance error", e);}// 如果注册成功,定时更新Metadata,没10s更新一次if(registered){executorRepository.nextScheduledExecutor().scheduleAtFixedRate(() -> {......try {// 刷新Metadata和ServiceInstanceServiceInstanceMetadataUtils.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#publishAppMetadatapublishMetadataToRemote(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 云原生之路的同学可以积极参与社区贡献!

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512504.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jquery将html转换word,HTML代码转word!亲测!可用!!!

现在项目中遇到一个需求&#xff0c;就是一个富文本编辑区中&#xff0c;有echars表格。用户点击保存按钮&#xff0c;需要导出为word文档。因为现在接手的项目&#xff0c;是基于上一个项目的框架。两个项目功能点差不多。但是在导出word这块&#xff0c;是后台java做的。也就…

智能搜索推荐一体化营收增长解决方案

简介&#xff1a; 图数据库GDB提供智能搜索推荐一站式服务&#xff0c;基于达摩院的智能搜索推荐算法和知识图谱技术&#xff0c;助力企业快速过渡冷启动过程&#xff0c;面向业务场景定制化方案&#xff0c;以提升核心业务指标&#xff0c;实现业务营收增长。 方案架构 方案特…

Redis 使用 List 实现消息队列的利与弊

作者 | 码哥字节 来源 | 码哥字节 分布式系统中必备的一个中间件就是消息队列&#xff0c;通过消息队列我们能对服务间进行异步解耦、流量消峰、实现最终一致性。 目前市面上已经有 RabbitMQ、RochetMQ、ActiveMQ、Kafka等&#xff0c;有人会问&#xff1a;“Redis 适合做消息队…

阿里云表格存储全面升级,打造一站式物联网存储新方案

简介&#xff1a; 阿里云表格存储全面升级&#xff0c;打造一站式物联网存储新方案 2021年9月1日&#xff0c;阿里云表格存储Tablestore重磅发布新能力&#xff1a;一站式物联网存储IoTstore。该新能力是阿里云表格存储Tablestore面向物联网深度垂直场景进行的一次技术升级&am…

手把手一起 图形化安装 k8s 集群

作者 | 小碗汤来源 | 我的小碗汤今天接着上一节&#xff0c;使用 KuboardSpray 图形化安装kubernetes集群[1]&#xff0c;记录了安装时可能遇到的问题。对此项目感兴趣的同学&#xff0c;不妨亲手实践一下~以下记录了安装单节点&#xff08;单master的集群&#xff09;&#xf…

Jaeger插件开发及背后的思考

简介&#xff1a; 本文主要介绍Jaeger最新的插件化后端的接口以及开发方法&#xff0c;让大家能够一步步的根据文章完成一个Jaeger插件的开发。此外SLS也推出了对于Jaeger的支持&#xff0c;欢迎大家试用。 随着云原生 微服务的推广和落地&#xff0c;服务监控也变得越来越重…

基于 MySQL + Tablestore 分层存储架构的大规模订单系统实践-架构篇

简介&#xff1a; 本文简要介绍了基于 MySQL 结合 Tablestore 的大规模订单系统方案。这种方案支持大数据存储、高性能数据检索、SQL搜索、实时与全量数据分析&#xff0c;且部署简单、运维成本低。 作者 | 弘楠 来源 | 阿里技术公众号 一 背景 订单系统存在于各行各业&#…

ajax返回来总是html,ajax返回类型

基于arcgis的webgis开发中目前是否还直接用ajax技本人是arcgis刚接触者&#xff0c;以前有听说过ajax这个技术&#xff0c;用于浏览器和web服务ajax技术现在依然是客户端浏览器和服务器交互的重要手段。 如果你用arcgis api for js技术&#xff0c;同样会使用ajax技术。这是良好…

三分钟教你用 Scarlet 写一个 WebSocket App

作者 | Eason来源 | 程序员巴士在移动应用程序中&#xff0c;数据层是屏幕上显示内容的真实来源。然而&#xff0c;在今年早些时候在 Tinder 中集成了 WebSocket API 时&#xff0c;维护它成为了一个令人头疼的问题。为了在 Android 上更轻松地集成 WebSocket&#xff0c;Scarl…

重磅发布|新一代云原生数据仓库AnalyticDB「SQL智能诊断」功能详解

简介&#xff1a; AnalyticDB For MySQL为用户提供了高效、实时、功能丰富并且智能化的「SQL智能诊断」和「SQL智能调优」功能&#xff0c;提供用户SQL性能调优的思路、方向和具体的方法&#xff0c;降低用户使用成本&#xff0c;提高用户使用ADB的效率 SQL是一种简单易用的业…

技术干货|基于Apache Hudi 的CDC数据入湖「内附干货PPT下载渠道」

简介&#xff1a; 阿里云技术专家李少锋(风泽)在Apache Hudi 与 Apache Pulsar 联合 Meetup 杭州站上的演讲整理稿件&#xff0c;本议题将介绍典型 CDC 入湖场景&#xff0c;以及如何使用 Pulsar/Hudi 来构建数据湖&#xff0c;同时将会分享 Hudi 内核设计、新愿景以及社区最新…

探究 Java 应用的启动速度优化

简介&#xff1a; 在高性能的背后&#xff0c;Java 的启动性能差也令人印象深刻&#xff0c;大家印象中的 Java 笨重缓慢的印象也大多来源于此。高性能和快启动速度似乎有一些相悖&#xff0c;本文将和大家一起探究两者是否可以兼得。 作者 | 梁希 高性能和快启动速度&#x…

阿里云刘伟光:金融核心系统将步入分布式智能化的时代

1月18日&#xff0c;阿里云在京发布金融核心系统转型“红宝书”&#xff0c;并推出“金融级云原生工场”&#xff0c;通过新的建设理念和相应的全链路平台技术&#xff0c;以及先进的部署体系&#xff0c;支撑金融机构建设面向未来的新一代分布式智能化核心系统。 阿里云智能新…

5分钟搞定Loki告警多渠道接入

简介&#xff1a; Loki是受Prometheus启发的水平可扩展、高可用、多租户日志聚合系统。用户既可以将Loki告警直接接入SLS开放告警&#xff0c;也可以先将Loki接入Grafana或Alert Manager&#xff0c;再借助Grafana或Alert Manager实现Loki间接接入SLS开放告警。 直接接入 您可…

当微服务遇上 Serverless | 微服务容器化最短路径,微服务 on Serverless 最佳实践

简介&#xff1a; 阿里云Serverless应用引擎&#xff08;SAE&#xff09;初衷是让客户不改任何代码&#xff0c;不改变应用部署方式&#xff0c;就可以享受到微服务K8sServerless的完整体验&#xff0c;开箱即用免运维。 前言 微服务作为一种更灵活、可靠、开放的架构&#x…

学计算机就业靠谱吗,2018年计算机专业就业怎么样?

由孙中山先生创办的至今已有一百多年办学传统&#xff0c;已经成为一所国内一流、国际知名的现代综合性大学。涉足的领域较广&#xff0c;有法律、医学等领域&#xff0c;每个领域都取得不俗的成绩。该校的计算机专业自开设以来也颇受学生欢迎&#xff0c;2018年计算机专业就业…

Serverless 工程实践 | 细数 Serverless 的配套服务

简介&#xff1a; 上文说到云计算的十余年发展让整个互联网行业发生了翻天覆地的变化&#xff0c;Serverless 作为云计算的产物&#xff0c;或者说是云计算在某个时代的表现&#xff0c;被很多人认为是真正意义上的云计算&#xff0c;关于“Serverless 是什么”这个问题&#x…

程序员在想些什么?拒绝盲猜,CSDN帮你精准洞察 Ta 们的心

CSDN 推出《开发者研究与洞察》服务。基于3200万开发者的资源&#xff0c;从开发者视角出发&#xff0c;聚焦开发者“关注”、“使用”、“体验”三方面&#xff0c;帮助技术推广者打造技术品牌、优化技术产品的市场投放策略、提升技术产品的开发者使用体验&#xff0c;直接聆听…

伴鱼:借助 Flink 完成机器学习特征系统的升级

简介&#xff1a; Flink 用于机器学习特征工程&#xff0c;解决了特征上线难的问题&#xff1b;以及 SQL Python UDF 如何用于生产实践。 本文作者陈易生&#xff0c;介绍了伴鱼平台机器学习特征系统的升级&#xff0c;在架构上&#xff0c;从 Spark 转为 Flink&#xff0c;解…

小型微型计算机系统退回修改,小型微型计算机系统

基本信息期刊名称小型微型计算机系统《中国计算机系统杂志》的英文名称出版周期每月发布了ISSN 1000-1220发布CN 21-1106 / TP邮政编码8-108组织者中国科学院沉阳计算技术研究所出版地: 辽宁省沉阳市期刊首页网址提交URL包含在中/荣誉CSCD核心期刊中国科学引文Pж(AJ)摘要杂志C…