深入解析 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 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×tamp=1629588251493
zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
- 订阅地址变更
核心代码在 FailbackRegistry#subscribe 和 ZookeeperRegistry#doSubscribe 中,具体如下:
1、FailbackRegistry#subscribe
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 云原生之路的同学可以积极参与社区贡献!
原文链接
本文为阿里云原创内容,未经允许不得转载。
深入解析 Dubbo 3.0 服务端暴露全流程的更多相关文章
- Swift3.0服务端开发(一) 完整示例概述及Perfect环境搭建与配置(服务端+iOS端)
本篇博客算是一个开头,接下来会持续更新使用Swift3.0开发服务端相关的博客.当然,我们使用目前使用Swift开发服务端较为成熟的框架Perfect来实现.Perfect框架是加拿大一个创业团队开发 ...
- Swift3.0服务端开发(五) 记事本的开发(iOS端+服务端)
前边以及陆陆续续的介绍了使用Swift3.0开发的服务端应用程序的Perfect框架.本篇博客就做一个阶段性的总结,做一个完整的实例,其实这个实例在<Swift3.0服务端开发(一)>这篇 ...
- oauth2.0服务端与客户端搭建
oauth2.0服务端与客户端搭建 - 推酷 今天搭建了oauth2.0服务端与客户端.把搭建的过程记录一下.具体实现的功能是:client.ruanwenwu.cn的用户能够通过 server.ru ...
- 4. 源码分析---SOFARPC服务端暴露
服务端的示例 我们首先贴上我们的服务端的示例: public static void main(String[] args) { ServerConfig serverConfig = new Ser ...
- 创建自己的OAuth2.0服务端(一)
如果对OAuth2.0有任何的疑问,请先熟悉OAuth2.0基础的文章:http://www.cnblogs.com/alunchen/p/6956016.html 1. 前言 本篇文章时对 客户端的 ...
- Zabbix5.0服务端部署
Zabbix5.0服务端部署 基础环境配置 [root@localhost ~]# systemctl disable --now firewalld Removed symlink /etc/sys ...
- netty服务端客户端启动流程分析
服务端启动流程 我们回顾前面讲解的netty启动流程,服务端这边有两个EventLoopGroup,一个专门用来处理连接,一个用来处理后续的io事件 服务端启动还是跟nio一样,绑定端口进行监听,我们 ...
- 源码分析--dubbo服务端暴露
服务暴露的入口方法是 ServiceBean 的 onApplicationEvent.onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务 ...
- Swift3.0服务端开发(二) 静态文件添加、路由配置以及表单提交
今天博客中就来聊一下Perfect框架的静态文件的添加与访问,路由的配置以及表单的提交.虽然官网上有聊静态文件的访问的部分,但是在使用Perfect框架来访问静态文件时还是有些点需要注意的,这些关键点 ...
- Swift3.0服务端开发(三) Mustache页面模板与日志记录
本篇博客主要介绍如果在Perfect工程中引入和使用Mustache页面模板与日志记录系统.Mustache页面模板类似于PHP中的smarty模板引擎或者Java中的JSTL标签.当然Mustach ...
随机推荐
- 【个人笔记】VBox7安装Debian网络下载慢问题处理
使用镜像安装Debian的过程中,会安装一些常用的软件包.但在安装软件包的阶段,默认情况下会通过网络进行下载.即使配置了国内的镜像,但是由于网络问题依然很慢.这个时候需要的在安装阶段选择从默认的DVD ...
- 《世嘉新人培训教材—游戏开发》2DGraphics1项目cmake构建
<世嘉新人培训教材-游戏开发>作为经典的游戏开发教程,提供了相关样例代码供我们进行开发使用.但是该样例是基于VS进行编写构建的,而本人日常喜欢CLion进行C/C++开发,于是准备使用cm ...
- FreeRTOS教程7 事件组
1.准备材料 正点原子stm32f407探索者开发板V2.4 STM32CubeMX软件(Version 6.10.0) Keil µVision5 IDE(MDK-Arm) 野火DAP仿真器 XCO ...
- 05.java多线程问题
目录介绍 5.0.0.1 线程池具有什么优点和缺点?为什么说开启大量的线程,会降低程序的性能,那么该如何做才能降低性能? 5.0.0.3 线程中start和run方法有什么区别?wait和sleep方 ...
- Mac M芯片使用PD安装centos7无页面安装
1.选择Centos镜像 点击继续 设置虚拟机名称: 点击创建 : 选择第一个回车开始下载系统,下载完成进入设置页面,首先输入 1 设置语言: 进入语言设置,选择77普通话: 选择c继续,又回到系统配 ...
- Scala 类和对象与Java的对比
一.包 1 package com{ 2 3 import com.atguigu.scala.Inner 4 5 // 在外层包中定义单例对象 6 object Outer{ 7 var out: ...
- #期望,树的直径#51nod 1803 森林直径
题目 有一棵 \(n\) 个结点的树,按顺序给出树边 \((fa[i],i)\), \(Q\) 次询问查询如果只选取第 \([l,r]\) 条树边,问森林的直径 \(fa[i]\) 的生成方式为 \( ...
- 使用OHOS SDK构建libsamplerate
参照OHOS IDE和SDK的安装方法配置好开发环境. 从github下载源码. 执行如下命令: git clone --depth=1 https://github.com/libsndfile/l ...
- 深入解析 Java 面向对象编程与类属性应用
Java 面向对象编程 面向对象编程 (OOP) 是一种编程范式,它将程序组织成对象.对象包含数据和操作数据的方法. OOP 的优势: 更快.更易于执行 提供清晰的结构 代码更易于维护.修改和调试 提 ...
- MFC程序隐藏托盘+右键关闭菜单
背景介绍: 我的程序是启动后,默认就隐藏到托盘中,等待http请求后,显示界面.所以最小化到托盘的代码,我是写在初始化里面. 正文: 一.自定义消息 WM_SHOWTASK #define W ...