Dubbo3.0应用级服务发现源码分析

发布一下 0 0

背景

阿里背书的Dubbo 3.0正式版在2021年3月份正式发布,Dubbo3.0核心功能包括:新一代RPC协议、应用级服务发现、新版路由规则等。随着云原生时代的到来,Dubbo 3.0 为了能够更好地适配云原生,3.0将原来的接口级服务发现演进为应用级服务发现

Dubbo 3.0应用级服务发现主要有以下优势:

  • 适配云原生微服务变革。云原生时代的基础设施能力不断向上释放,像 Kubernetes 等平台都集成了微服务概念抽象,Dubbo3.0的应用级服务发现是适配各种微服务体系的通用模型(来自Dubbo官网数据)
  • 提升性能与可伸缩性。支持超大规模集群的服务治理一直以来都是 Dubbo 的优势,通过引入应用级服务发现模型,从本质上解决了注册中心地址数据的存储与推送压力,相应的 Consumer 侧的地址计算压力也成数量级下降;集群规模也开始变得可预测、可评估(与 RPC 接口数量无关,只与实例部署规模相关)。能支持的集群实例规模以百万计的集群注册中心总体数据量下降超 60%,极限场景下下降超90%(来自Dubbo官网数据)

目前关于Dubbo服务端暴露流程的技术文章是基于Dubbo接口级服务发现机制来解读的。在Dubbo3.0的应用级服务发现机制下,服务端暴露流程与之前有很大的变化,而且我们团队也在使用Dubbo3+Nacos2做微服务改造,已积累了一定的实战经验和对Dubbo3.0应用级服务发现有了更深入的了解,本文希望可以通过对Dubbo 3.0源码理解来解析服务端暴露全流程。


概念

什么是应用级服务发现?


Dubbo3.0主要解决的问题:

  • 对齐主流微服务模型,如:Spring Cloud
  • 减少注册中心数据存储能力,降低了地址变更推送的压力

应用级服务与接口级服务的区别

假设应用app-user部署了3个实例(user1, user2, user3),并且对外提供了 3 个接口(UserApi, RoleApi, MenuApi)。在接口级和应用级服务发现机制下,注册到注册中心的数据是截然不同的。如下图所示:

  • 接口级服务发现机制下注册中心中的数据
"UserApi":[    {"instanceId":"instance1","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"side":"provider","service.name":"ServiceBean:/×××.×××.×××.×××.UserApi:1.0.0","methods":"getById","release":"2.7.13","deprecated":false,"dubbo":"2.0.2","pid":1,"interface":"×××.×××.×××.×××.SysUserApi","version":"1.0.0","generic":false,"revision":"1.0.0","path":"×××.×××.×××.×××.SysUserApi","protocol":"dubbo","application":"annotation-user","dynamic":true,"category":"providers","anyhost":true,"timestamp":1662031063698}},    {"instanceId":"instance2","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"side":"provider","service.name":"ServiceBean:/×××.×××.×××.×××.UserApi:1.0.0","methods":"getById","release":"2.7.13","deprecated":false,"dubbo":"2.0.2","pid":1,"interface":"×××.×××.×××.×××.SysUserApi","version":"1.0.0","generic":false,"revision":"1.0.0","path":"×××.×××.×××.×××.SysUserApi","protocol":"dubbo","application":"annotation-user","dynamic":true,"category":"providers","anyhost":true,"timestamp":1662031063698}},    {"instanceId":"instance2","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"side":"provider","service.name":"ServiceBean:/×××.×××.×××.×××.UserApi:1.0.0","methods":"getById","release":"2.7.13","deprecated":false,"dubbo":"2.0.2","pid":1,"interface":"×××.×××.×××.×××.SysUserApi","version":"1.0.0","generic":false,"revision":"1.0.0","path":"×××.×××.×××.×××.SysUserApi","protocol":"dubbo","application":"annotation-user","dynamic":true,"category":"providers","anyhost":true,"timestamp":1662031063698}}],"RoleApi":[    {"instanceId":"instance1","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"side":"provider","service.name":"ServiceBean:/×××.×××.×××.×××.RoleApi:1.0.0","methods":"selectById","release":"2.7.13","deprecated":false,"dubbo":"2.0.2","pid":1,"interface":"×××.×××.×××.×××.SysUserApi","version":"1.0.0","generic":false,"revision":"1.0.0","path":"×××.×××.×××.×××.SysUserApi","protocol":"dubbo","application":"annotation-user","dynamic":true,"category":"providers","anyhost":true,"timestamp":1662031063698}},    {"instanceId":"instance2","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"side":"provider","service.name":"ServiceBean:/×××.×××.×××.×××.RoleApi:1.0.0","methods":"selectById","release":"2.7.13","deprecated":false,"dubbo":"2.0.2","pid":1,"interface":"×××.×××.×××.×××.SysUserApi","version":"1.0.0","generic":false,"revision":"1.0.0","path":"×××.×××.×××.×××.SysUserApi","protocol":"dubbo","application":"annotation-user","dynamic":true,"category":"providers","anyhost":true,"timestamp":1662031063698}},    {"instanceId":"instance2","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"side":"provider","service.name":"ServiceBean:/×××.×××.×××.×××.RoleApi:1.0.0","methods":"selectById","release":"2.7.13","deprecated":false,"dubbo":"2.0.2","pid":1,"interface":"×××.×××.×××.×××.SysUserApi","version":"1.0.0","generic":false,"revision":"1.0.0","path":"×××.×××.×××.×××.SysUserApi","protocol":"dubbo","application":"annotation-user","dynamic":true,"category":"providers","anyhost":true,"timestamp":1662031063698}}],"MenuApi":[    {"instanceId":"instance1","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"side":"provider","service.name":"ServiceBean:/×××.×××.×××.×××.MenuApi:1.0.0","methods":"selectByProjectId","release":"2.7.13","deprecated":false,"dubbo":"2.0.2","pid":1,"interface":"×××.×××.×××.×××.SysUserApi","version":"1.0.0","generic":false,"revision":"1.0.0","path":"×××.×××.×××.×××.SysUserApi","protocol":"dubbo","application":"annotation-user","dynamic":true,"category":"providers","anyhost":true,"timestamp":1662031063698}},    {"instanceId":"instance2","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"side":"provider","service.name":"ServiceBean:/×××.×××.×××.×××.MenuApi:1.0.0","methods":"selectByProjectId","release":"2.7.13","deprecated":false,"dubbo":"2.0.2","pid":1,"interface":"×××.×××.×××.×××.SysUserApi","version":"1.0.0","generic":false,"revision":"1.0.0","path":"×××.×××.×××.×××.SysUserApi","protocol":"dubbo","application":"annotation-user","dynamic":true,"category":"providers","anyhost":true,"timestamp":1662031063698}},    {"instanceId":"instance2","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"side":"provider","service.name":"ServiceBean:/×××.×××.×××.×××.MenuApi:1.0.0","methods":"selectByProjectId","release":"2.7.13","deprecated":false,"dubbo":"2.0.2","pid":1,"interface":"×××.×××.×××.×××.SysUserApi","version":"1.0.0","generic":false,"revision":"1.0.0","path":"×××.×××.×××.×××.SysUserApi","protocol":"dubbo","application":"annotation-user","dynamic":true,"category":"providers","anyhost":true,"timestamp":1662031063698}}]
  • 应用级服务发现机制下注册中心中的数据
"annotation-user":{[    {"instanceId":"instance1","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"dubbo.metadata-service.url-params":{"connections":"1","version":"1.0.0","dubbo":"2.0.2","release":"3.0.10","side":"provider","port":"20880","protocol":"dubbo"},"dubbo.endpoints":[{"port":30002,"protocol":"tri"}],"dubbo.metadata.revision":"31e476788487b90a0ca94b53bccb656c","dubbo.metadata.storage-type":"local","timestamp":1662343919223}},    {"instanceId":"instance2","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"dubbo.metadata-service.url-params":{"connections":"1","version":"1.0.0","dubbo":"2.0.2","release":"3.0.10","side":"provider","port":"20880","protocol":"dubbo"},"dubbo.endpoints":[{"port":30002,"protocol":"tri"}],"dubbo.metadata.revision":"31e476788487b90a0ca94b53bccb656c","dubbo.metadata.storage-type":"local","timestamp":1662343919223}},    {"instanceId":"instance2","ip":"×××.×××.×××.×××","port":"30002","weight":1,"ephemeral":true,"metadata":{"dubbo.metadata-service.url-params":{"connections":"1","version":"1.0.0","dubbo":"2.0.2","release":"3.0.10","side":"provider","port":"20880","protocol":"dubbo"},"dubbo.endpoints":[{"port":30002,"protocol":"tri"}],"dubbo.metadata.revision":"31e476788487b90a0ca94b53bccb656c","dubbo.metadata.storage-type":"local","timestamp":1662343919223}}]}

通过对比我们可以发现,采用应用级服务发现机制确实使注册中心中的数据量减少了很多,那些原有的接口级的数据存储在元数据中心中。

协议的服务初始化

注:接下来的代码为dubbo3.0.10版本为例(注册中心、配置中心选用Nacos2.1.0版本)


引入应用级服务发现机制以后,Dubbo 3.0 服务端暴露全流程和之前有很大的区别。

暴露服务端全流程从DubboDeployApplicationListener#onApplicationEvent中开始,整体链路如下:

Dubbo3.0应用级服务发现源码分析

Dubbo3.0应用级服务发现源码分析

具体如下:

// org.apache.dubbo.config.spring.context.DubboDeployApplicationListener@Overridepublic void onApplicationEvent(ApplicationContextEvent event) {    if (nullSafeEquals(applicationContext, event.getSource())) {        if (event instanceof ContextRefreshedEvent) {  // 上下文刷新事件            onContextRefreshedEvent((ContextRefreshedEvent) event);        } else if (event instanceof ContextClosedEvent) {            onContextClosedEvent((ContextClosedEvent) event);        }    }}//org/apache/dubbo/config/ServiceConfigprotected synchronized void doExport() {    ......    doExportUrls(); // 协议的服务初始化,    exported();  // 应用级服务注册、元数据发布}


  1. 协议服务初始化、元数据封装、服务注册/发布的整体入口从监听Spring上下文刷新事件开始 注:onApplicationEvent是一个Spring上下文刷新事件的通知类,DubboDeployApplicationListener类实现了ApplicationListener<ApplicationContextEvent>,通过一个事件监听器开始dubbo服务注册
  2. onContextRefreshedEvent最终会调用到ServiceConfig#doExport
  3. doExportUrls:协议的服务初始化
  4. exported:应用级服务注册/元数据发布


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

  • injvm协议的服务初始化
  • service-discovery-registry协议的服务初始化
  • triple协议的服务初始化

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


  • injvm协议的服务初始化


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


其核心代码:ServiceConfig#exportLocal开始初始化injvm协议的本地服务

//org/apache/dubbo/config/ServiceConfigprivate void exportUrl(URL url, List<URL> registryURLs) {    String scope = url.getParameter(SCOPE_KEY);    if (!SCOPE_NONE.equalsIgnoreCase(scope)) {        if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {            //injvm协议的服务初始化            exportLocal(url);         }        if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {            // service-discover-registry协议的服务初始化            url = exportRemote(url, registryURLs);            if (!isGeneric(generic) && !getScopeModel().isInternal()) {                // 接口元数据信息发布                MetadataUtils.publishServiceDefinition(url, providerModel.getServiceModel(), getApplicationModel());            }        }    }    this.urls.add(url);}//org/apache/dubbo/config/ServiceConfigprivate void exportLocal(URL url) {    URL local = URLBuilder.from(url)        .setProtocol(LOCAL_PROTOCOL)  // 设置协议为:injvm        .setHost(LOCALHOST_VALUE)  // host:127.0.0.1        .setPort(0)        .build();    local = local.setScopeModel(getScopeModel())        .setServiceModel(providerModel);    doExportUrl(local, false);  //暴露本地服务    ......}

初始化到本地的核心作用:

  1. 创建InjvmProtocol对象将其放入到exporterMap中
  2. 创建ListenerExporterWrapper对象,将InjvmProtocol对象将封装进去,然后ListenerExporterWrapper对象存入到exporters List中


  • service-discovery-registry协议服务初始化

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


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

  • 注册 service-discovery-registry 协议的入口
//org/apache/dubbo/config/ServiceConfigprivate URL exportRemote(URL url, List<URL> registryURLs) {    if (CollectionUtils.isNotEmpty(registryURLs)) {    // 如果是多个注册中心,通过循环对每个注册中心进行注册        for (URL registryURL : registryURLs) {            // 一系列封装url逻辑,如:            // 将service-name-mapping=true加入到url中            //if protocol is only injvm ,not register            // 创建monitorURL,将其加入url中(如果没有配置monitor,则为null)            // For providers, this is used to enable custom proxy to generate invoker            ......            //核心逻辑:注册service-discovery-registry协议复用服务暴露流程            doExportUrl(registryURL.putAttribute(EXPORT_KEY, url), true);        }        ......    } else {        ......    }    return url;}


  • 将invoker包装成exporter,然后放入到exporters
//org/apache/dubbo/config/ServiceConfigprivate void doExportUrl(URL url, boolean withMetaData) {    //为实现类创建Invoker,类型:JavassistProxyFactory    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);    if (withMetaData) {        // 将invoker包装成DelegateProviderMetaDataInvoker        invoker = new DelegateProviderMetaDataInvoker(invoker, this);    }    //protocolSPI:协议自定义适配器,根据协议动态加载实现类    //此时exporter为ProtocolSerializationWrapper对象    Exporter<?> exporter = protocolSPI.export(invoker);    // 将导出服务添加到exporters中    exporters.add(exporter);}


  • 通过 RegistryProtocol 将 Invoker 转化成 Exporter

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

// org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper@Overridepublic <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 中,具体如下:

// org.apache.dubbo.registry.integration.RegistryProtocol@Overridepublic <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {    URL registryUrl = getRegistryUrl(originInvoker);    URL providerUrl = getProviderUrl(originInvoker);    ......    //创建ProviderConfigurationListener对象,缓存到本地registeredBeanInfos list中    Map<URL, NotifyListener> overrideListeners = getProviderConfigurationListener(providerUrl).getOverrideListeners();    // 将overrideSubscribeListener(默认为空)放入到overrideListeners map中,    overrideListeners.put(registryUrl, overrideSubscribeListener);    // 从配置中心拉取数据,覆盖providerUrl属性    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);    //暴露Triple协议的服务    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);    // registryUrl中包含service-discovery-registry协议    // 通过该协议创建ServiceDiscoveryRegistry对象    // 最后包装成ListenerRegistryWrapper对象    final Registry registry = getRegistry(registryUrl);    final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);    boolean register = providerUrl.getParameter(REGISTER_KEY, true) && registryUrl.getParameter(REGISTER_KEY, true);    if (register) {        // 注册service-discovery-registry协议        register(registry, registeredProviderUrl);    }    .......    // 触发RegistryServiceListener的onRegister事件    notifyExport(exporter);    .......}


步骤:

  1. getRegistryUrl:创建service-discovery-register协议URL
  2. getProviderUrl:创建triple协议URL
  3. getProviderConfigurationListener:1. 初始化ProviderConfigurationListener类型监听器,将ProviderConfigurationListener对象缓存到registeredBeanInfos list中。2. ProviderConfigurationListener从Nacos配置中心拉取ProviderConfiguration相关的配置,供Provider使用。(注:如果有需要,可以在dataId:annotation-user.configurators,group:dubbo 配置文件中自定义一些provider相关的配置)
  4. overrideUrlWithConfig:1. 创建serviceConfigurationListener,将serviceConfigurationListener缓存到serviceConfigurationListeners map中。2. serviceConfigurationListener从nacos拉取服务配置信息(入参:dataId:******.UserApi:version:.configurators,group:dubbo)。根据Nacos配置覆盖providerUrl配置
  5. doLocalExport:暴露Triple协议的服务。1. 创建ExporterChangeableWrapper对象,将其放入到bounds map中。2. 创建ListenerExporterWrapper对象将其封装到ExporterChangeableWrapper对象中,将TripleProtocol对象封装到ListenerExporterWrapper对象中
  6. getRegistry:registryUrl是service-discovery-registry协议URL。1. 创建协议对应的ServiceDiscoveryRegistry对象,将其放入registries map中。2. 创建RegistryFactoryWrapper对象最终会创建ListenerRegistryWrapper对象,同时从Nacos配置中心拉取配置信息,进行数据封装


  • 注册Triple 协议的服务

核心代码在 RegistryProtocol#doLocalExport 中,具体如下:

// org.apache.dubbo.registry.integration.RegistryProtocolprivate <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {    String key = getCacheKey(originInvoker);    return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {        Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);        // 最终会调用TripleProtocol#export(Invoker<T> invoker)方法        return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);    });}// org.apache.dubbo.rpc.protocol.tri.TripleProtocolpublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {    ......    // 将tri协议缓存到exporterMap中(如:key=ai.advance.guardian.annotation.api.SysUserApi:1.0.0:30002)    // exporter类型为TripleProtocol    exporterMap.put(key, exporter);    // 将invoker放入到invokers Set中,invoker类型为:FilterChainBuilder.CallbackRegistrationInvoker    invokers.add(invoker);    // 将invoker放入到pathResolver中(如:key=ai.advance.guardian.annotation.api.SysUserApi:1.0.0)    // pathResolver类型为TriplePathResolver,初始化TripleProtocol时通过SPI创建pathResolver    pathResolver.add(url.getServiceKey(), invoker);    // key=ai.advance.guardian.annotation.api.SysUserApi    pathResolver.add(url.getServiceModel().getServiceModel().getInterfaceName(), invoker);    ......    url.getOrDefaultApplicationModel().getExtensionLoader(ExecutorRepository.class)        .getDefaultExtension()// 通过SPI获取ExecutorRepository        .createExecutorIfAbsent(url);  // 给接口创建执行线程池,然后放入到executors map中    PortUnificationExchanger.bind(url);    optimizeSerialization(url);    return exporter;}

注:

  1. protocol为ProtocolSerializationWrapper类型对象,逻辑跟injvm协议的服务注册基本一致,不过暴露Triple协议的服务最终会创建TripleProtocol对象
  2. TripleProtocol中将export、invoker等放入map中


  • 注册service-discovery-registry协议

核心代码在 ServiceDiscoveryRegistry#register和MetadataInfo#addService 中,具体如下:

// org.apache.dubbo.registry.client.ServiceDiscoveryRegistrypublic final void register(URL url) {    if (!shouldRegister(url)) { // Should Not Register        return;    }    // 注册service-discovery-registry协议    doRegister(url);}// org.apache.dubbo.metadata.MetadataInfopublic synchronized void addService(URL url) {    // fixme, pass in application mode context during initialization of MetadataInfo.    if (this.loader == null) {        this.loader = url.getOrDefaultApplicationModel().getExtensionLoader(MetadataParamsFilter.class);    }    List<MetadataParamsFilter> filters = loader.getActivateExtension(url, "params-filter");    // generate service level metadata    ServiceInfo serviceInfo = new ServiceInfo(url, filters);    // 将serviceInfo放入到services map中    this.services.put(serviceInfo.getMatchKey(), serviceInfo);    // extract common instance level params    extractInstanceParams(url, filters);    if (exportedServiceURLs == null) {        exportedServiceURLs = new ConcurrentSkipListMap<>();    }    // 将url放入到exportedServiceURLs map中    addURL(exportedServiceURLs, url);    updated = true;}

注:

ServiceDiscoveryRegistry#doRegister最终会调用到MetadataInfo#addService

缓存service-discovery-registry协议URL到exportedServiceURLs map中

缓存serviceInfo到services map中


  • 发布服务注册事件

核心代码在 RegistryProtocol#notifyExport 中,具体如下:

// org.apache.dubbo.registry.integration.RegistryProtocolprivate <T> void notifyExport(ExporterChangeableWrapper<T> exporter) {    ScopeModel scopeModel = exporter.getRegisterUrl().getScopeModel();    List<RegistryProtocolListener> listeners = ScopeModelUtil.getExtensionLoader(RegistryProtocolListener.class, scopeModel)        .getActivateExtension(exporter.getOriginInvoker().getUrl(), REGISTRY_PROTOCOL_LISTENER_KEY);    if (CollectionUtils.isNotEmpty(listeners)) {        for (RegistryProtocolListener listener : listeners) {            // 发布RegistryProtocolListener的onExport事件            listener.onExport(this, exporter);        }    }}

注:

  1. 我们可以看出注册 service-discovery-registry 协议的核心目的是为了将服务的接口相关的信息存储在内存中。从兼容性和平滑迁移两方面来考虑,在实现的时候采取复用 ServiceConfig 的暴露流程的方式。
  2. 通过SPI方式动态创建RegistryProtocolListener的实现类是MigrationRuleListener,构造函数中会调用Nacos配置中心配置信息(订阅规则信息),如果应用中有订阅者则可以规则改变,进而概念调用者的行为。


  • triple协议服务初始化

由于暴露 Triple 协议服务的流程和暴露 Injvm 协议服务的流程是一致的,所以不再赘述。


  • 协议的服务初始化总结:


  1. 代码中大量运用了Dubbo SPI方式动态加载实现类,相比Java SPI,Dubbo SPI加载实现类更加灵活,不过代码没有运行起来的情况下可读性稍差
  2. 协议的服务初始化主要目的是初始化注册、服务相关的元数据,以供元数据发布、服务注册时使用,不过也可以从Nacos配置中心读取数据,进行初始化


应用级服务注册/发布

服务注册/发布包含3部分:

  1. 接口元数据发布
  2. 应用映射接口数据发布
  3. 应用服务注册


  • 接口元数据发布


元数据分两种类型:

  1. 注册中心的应用实例中的元数据信息(protocol、version相关信息)
  2. 配置中心的接口相关的元数据信息(接口、方法、host、port、version等等相关信息)


此处将接口元数据发布到配置中心

因为在前面完成了injvm、service-discovery-registry、tri等等协议服务初始化工作之后基本完成了MetaData、ServiceBean的初始化工作,所以接下来的口元数据发布到配置中心比较简单

Dubbo3.0应用级服务发现源码分析

核心代码从MetadataUtils#publishServiceDefinition开始,具体如下:

// org.apache.dubbo.registry.client.metadata.MetadataUtilspublic static void publishServiceDefinition(URL url, ServiceDescriptor serviceDescriptor, ApplicationModel applicationModel) {    ......    String side = url.getSide();    if (PROVIDER_SIDE.equalsIgnoreCase(side)) {        String serviceKey = url.getServiceKey();        FullServiceDefinition serviceDefinition = serviceDescriptor.getFullServiceDefinition(serviceKey);        if (StringUtils.isNotEmpty(serviceKey) && serviceDefinition != null) {            serviceDefinition.setParameters(url.getParameters());            for (Map.Entry<String, MetadataReport> entry : getMetadataReports(applicationModel).entrySet()) {                MetadataReport metadataReport = entry.getValue();                if (!metadataReport.shouldReportDefinition()) {                    logger.info("Report of service definition is disabled for " + entry.getKey());                    continue;                }                metadataReport.storeProviderMetadata(                    new MetadataIdentifier(                        url.getServiceInterface(),                        url.getVersion() == null ? "" : url.getVersion(),                        url.getGroup() == null ? "" : url.getGroup(),                        PROVIDER_SIDE,                        applicationModel.getApplicationName())                    , serviceDefinition);            }        }    }    ......}//org/apache/dubbo/metadata/report/support/AbstractMetadataReportprivate void storeProviderMetadataTask(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) {    try {        ......        allMetadataReports.put(providerMetadataIdentifier, serviceDefinition);        failedReports.remove(providerMetadataIdentifier);        String data = JsonUtils.getJson().toJson(serviceDefinition);        //         doStoreProviderMetadata(providerMetadataIdentifier, data);        saveProperties(providerMetadataIdentifier, data, true, !syncReport);    } catch (Exception e) {        ......    }}

步骤:

  1. publishServiceDefinition:getMetadataReports(applicationModel)获取接口元数据信息(该接口元数据通过DefaultApplicationDeployer#startMetadataCenter方法加载,其实在DeubboConfigBean初始化阶段已完成MetadataCenter数据初始化)。
  2. storeProviderMetadata:去发布接口的元数据。1. storeProviderMetadataTask:将serviceDefinition放入到map allMetadataReports中。(以providerMetadataIdentifier作为key)生成元数据json字符串data)。2. storeMetadata:开始发布元数据信息(拼接生成dataId,拼接方法:${serviceInterface}:${version}:${group}:${side}:${applicationName},如:×××.×××.×××.UserApi:1.0.0::provider:application-name。 注:初始化MetadataIdentifier时,group为空)。
  3. publishConfigInner:调用nacos-client接口发布元数据信息,在nacos配置列表,指定namespace、指定group下创建配置数据
  4. saveProperties:将元数据信息放入到properties中,既本地缓存doSaveProperties:将元数据存储到本地文件中,文件为:/Users/advance/.dubbo/dubbo-metadata-application-name-×××.×××.×××.×××-8848.cache


  • 应用映射接口数据发布


因为在前面已经完成了MetaData、ServiceBean的初始化工作的初始化工作,所以到这里,应用映射接口数据发布过程其实比较简单


数据格式如下:

{    "dataId":"×××.×××.×××.UserApi",    "group":"mapping",    "content":"application-name"}
Dubbo3.0应用级服务发现源码分析

从ServiceConfig#exported开启"应用映射接口数据"发布,以下是一些核心逻辑

// 调用来源为ServiceConfig.doExport()// org.apache.dubbo.config.spring.ServiceBeanprotected void exported() {    // 调用ServiceConfig.exported方法    super.exported();    // Publish ServiceBeanExportedEvent    publishExportEvent();}// org/apache/dubbo/config/ServiceConfigprotected void exported() {    exported = true;    List<URL> exportedURLs = this.getExportedUrls();    exportedURLs.forEach(url -> {        if (url.getParameters().containsKey(SERVICE_NAME_MAPPING_KEY)) {            // 通过SPI方式动态获取MetadataServiceNameMapping对象            ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension(getScopeModel());            try {                // 封装应用映射接口数据                boolean succeeded = serviceNameMapping.map(url);                ......            } catch (Exception e) {                logger.error("Failed register interface application mapping for service " + url.getServiceKey(), e);            }        }    });    ......}// org.apache.dubbo.metadata.store.nacos.NacosMetadataReport@Overridepublic boolean registerServiceAppMapping(String key, String group, String content, Object ticket) {    try {        if (!(ticket instanceof String)) {            throw new IllegalArgumentException("nacos publishConfigCas requires string type ticket");        }        //往nacos配置中心注册应用映射数据        return configService.publishConfigCas(key, group, content, (String) ticket);    } catch (NacosException e) {        logger.warn("nacos publishConfigCas failed.", e);        return false;    }}// org.apache.dubbo.metadata.store.nacos.NacosConfigServiceWrapperpublic boolean publishConfigCas(String dataId, String group, String content, String casMd5) throws NacosException {    //调用nacos接口往配置中心注册应用映射数据    return configService.publishConfigCas(handleInnerSymbol(dataId), handleInnerSymbol(group), content, casMd5);}


  1. exported:应用映射接口数据。1. 获取到已初始化的exportedURLs。2. serviceNameMapping.map最终会调用到NacosMetadataReport#registerServiceAppMapping方法(往配置中心发布应用映射接口数据)
  2. publishExportEvent:发布导出应用事件


  • 应用级服务注册

注册应用级服务是Dubbo3.0服务暴露的核心流程,也就是之前提到的应用级服务发现机制。

相比接口元数据的发布和应用映射接口数据的发布,应用级服务注册放在了最后执行

Dubbo3.0应用级服务发现源码分析

  • ServiceInstance封装

应用级服务的注册从DefaultApplicationDeployer#prepareApplicationInstance开始,以下是一些核心逻辑:

// org.apache.dubbo.config.deploy.DefaultApplicationDeployerpublic void prepareApplicationInstance() {    ......    if (isRegisterConsumerInstance()) {        exportMetadataService();        if (hasPreparedApplicationInstance.compareAndSet(false, true)) {            // 最终会调用到AbstractServiceDiscovery#register()            registerServiceInstance();        }    }}// org.apache.dubbo.registry.clien.AbstractServiceDiscoverypublic synchronized void register() throws RuntimeException {    // 创建服务实例对象    this.serviceInstance = createServiceInstance(this.metadataInfo);    if (!isValidInstance(this.serviceInstance)) {        return;    }    // 创建元数据版本号    boolean revisionUpdated = calOrUpdateInstanceRevision(this.serviceInstance);    if (revisionUpdated) {        ......        doRegister(this.serviceInstance); // 应用注册    }}protected ServiceInstance createServiceInstance(MetadataInfo metadataInfo) {    DefaultServiceInstance instance = new DefaultServiceInstance(serviceName, applicationModel);    instance.setServiceMetadata(metadataInfo);    // 设置dubbo.metadata.storage-type:local/remote,local:存储在应用中,remote:存储在配置中心    setMetadataStorageType(instance, metadataType);    // 封装元数据,例如:timestamp=1662343919223    ServiceInstanceMetadataUtils.customizeInstance(instance, applicationModel);    return instance;}

注:

从DefaultModuleDeployer#start()的onModuleStarted()调用开始应用服务注册

步骤:

  1. prepareApplicationInstance:开始应用注册
  2. registerMetadataAndInstance:1. 从BeanFactory获取registryManager,获取ServiceDiscoveries。2. 调用serviceDiscovery.register方法,开始注册
  3. createServiceInstance:1. 创建DefaultServiceInstance对象。2. 设置元数据(如:dubbo.metadata.storage-type:local/remote,local:存储在应用中,remote:存储在配置中心)
  4. calOrUpdateInstanceRevision:创建元数据版本号
  5. reportMetadata: 判断是否需要将元数据发布到配置中心


  • 向Nacos注册dubbo应用
// org.apache.dubbo.registry.nacos.NacosServiceDiscovery@Overridepublic void doRegister(ServiceInstance serviceInstance) {    execute(namingService, service -> {        Instance instance = toInstance(serviceInstance);        service.registerInstance(instance.getServiceName(), Constants.DEFAULT_GROUP, instance);    });}public void registerInstance(String serviceName, String group, Instance instance) throws NacosException {    namingService.registerInstance(handleInnerSymbol(serviceName), group, instance);}

步骤:

  1. doRegister:异步向注册dubbo应用
  2. 将DefaultServiceInstance serviceInstance封装成Instance instance对象,形成最终的应用注册信息,如下所示。
  3. registerInstance:dubbo应用group默认为:DEFAULT_GROUP


最终注册的dubbo应用信息

Dubbo3.0应用级服务发现源码分析

Dubbo应用注册和Spring Boot应用注册区别

  1. Instance中metadata(Dubbo有5项数据、Spring Boot只有一项数据)
  2. Spring Boot注册group可以自定义,Dubbo不能自定义,默认为DEFAULT_GROUP
  3. 应用注册触发机制不同


总结

通过对Dubbo 3.0服务端暴露全流程的解析发现,应用级服务发现机制的实现相对复杂,本文时序图以及代码梳理仅仅梳理核心又关键的逻辑点,想要深入理解Dubbo3.0应用级服务注册,还需要自己动手深入了解


最后由于本人能力有限,如果有错误的地方,请指出!

版权声明:内容来源于互联网和用户投稿 如有侵权请联系删除

本文地址:http://0561fc.cn/159523.html