背景 阿里背书的Dubbo3。0正式版在2021年3月份正式发布,Dubbo3。0核心功能包括:新一代RPC协议、应用级服务发现、新版路由规则等。随着云原生时代的到来,Dubbo3。0为了能够更好地适配云原生,3。0将原来的接口级服务发现演进为应用级服务发现。 Dubbo3。0应用级服务发现主要有以下优势:适配云原生微服务变革。云原生时代的基础设施能力不断向上释放,像Kubernetes等平台都集成了微服务概念抽象,Dubbo3。0的应用级服务发现是适配各种微服务体系的通用模型(来自Dubbo官网数据)提升性能与可伸缩性。支持超大规模集群的服务治理一直以来都是Dubbo的优势,通过引入应用级服务发现模型,从本质上解决了注册中心地址数据的存储与推送压力,相应的Consumer侧的地址计算压力也成数量级下降;集群规模也开始变得可预测、可评估(与RPC接口数量无关,只与实例部署规模相关)。能支持的集群实例规模以百万计的集群注册中心总体数据量下降超60,极限场景下下降超90(来自Dubbo官网数据) 目前关于Dubbo服务端暴露流程的技术文章是基于Dubbo接口级服务发现机制来解读的。在Dubbo3。0的应用级服务发现机制下,服务端暴露流程与之前有很大的变化,而且我们团队也在使用Dubbo3Nacos2做微服务改造,已积累了一定的实战经验和对Dubbo3。0应用级服务发现有了更深入的了解,本文希望可以通过对Dubbo3。0源码理解来解析服务端暴露全流程。 概念什么是应用级服务发现? Dubbo3。0主要解决的问题:对齐主流微服务模型,如:SpringCloud减少注册中心数据存储能力,降低了地址变更推送的压力应用级服务与接口级服务的区别 假设应用appuser部署了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:annotationuser,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:annotationuser,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:annotationuser,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:annotationuser,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:annotationuser,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:annotationuser,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:annotationuser,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:annotationuser,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:annotationuser,dynamic:true,category:providers,anyhost:true,timestamp:1662031063698}}〕应用级服务发现机制下注册中心中的数据annotationuser:{〔{instanceId:instance1,ip:。。。,port:30002,weight:1,ephemeral:true,metadata:{dubbo。metadataservice。urlparams:{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。storagetype:local,timestamp:1662343919223}},{instanceId:instance2,ip:。。。,port:30002,weight:1,ephemeral:true,metadata:{dubbo。metadataservice。urlparams:{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。storagetype:local,timestamp:1662343919223}},{instanceId:instance2,ip:。。。,port:30002,weight:1,ephemeral:true,metadata:{dubbo。metadataservice。urlparams:{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。storagetype:local,timestamp:1662343919223}}〕} 通过对比我们可以发现,采用应用级服务发现机制确实使注册中心中的数据量减少了很多,那些原有的接口级的数据存储在元数据中心中。协议的服务初始化 注:接下来的代码为dubbo3。0。10版本为例(注册中心、配置中心选用Nacos2。1。0版本) 引入应用级服务发现机制以后,Dubbo3。0服务端暴露全流程和之前有很大的区别。 暴露服务端全流程从DubboDeployApplicationListeneronApplicationEvent中开始,整体链路如下: 具体如下:org。apache。dubbo。config。spring。context。DubboDeployApplicationListenerOverridepublicvoidonApplicationEvent(ApplicationContextEventevent){if(nullSafeEquals(applicationContext,event。getSource())){if(eventinstanceofContextRefreshedEvent){上下文刷新事件onContextRefreshedEvent((ContextRefreshedEvent)event);}elseif(eventinstanceofContextClosedEvent){onContextClosedEvent((ContextClosedEvent)event);}}}orgapachedubboconfigServiceConfigprotectedsynchronizedvoiddoExport(){。。。。。。doExportUrls();协议的服务初始化,exported();应用级服务注册、元数据发布} 协议服务初始化、元数据封装、服务注册发布的整体入口从监听Spring上下文刷新事件开始注:onApplicationEvent是一个Spring上下文刷新事件的通知类,DubboDeployApplicationListener类实现了ApplicationListener,通过一个事件监听器开始dubbo服务注册onContextRefreshedEvent最终会调用到ServiceConfigdoExportdoExportUrls:协议的服务初始化exported:应用级服务注册元数据发布 我们可以看到,整个的注册流程还是挺复杂的,一共可以分为四个部分:injvm协议的服务初始化servicediscoveryregistry协议的服务初始化triple协议的服务初始化 下面会分别从这四个部分对服务暴露全流程进行详细讲解。 injvm协议的服务初始化 injvm协议的服务是暴露在本地的,主要原因是在一个应用上往往既有Service(暴露服务)又有Reference(服务引用)的情况存在,并且Reference引用的服务就是在该应用上暴露的Service。为了支持这种使用场景,Dubbo提供了injvm协议,将Service暴露在本地,Reference就可以不需要走网络直接在本地调用Service,其本质主要逻辑就是封装injvm的URL,缓存在本地 其核心代码:ServiceConfigexportLocal开始初始化injvm协议的本地服务orgapachedubboconfigServiceConfigprivatevoidexportUrl(URLurl,ListURLregistryURLs){Stringscopeurl。getParameter(SCOPEKEY);if(!SCOPENONE。equalsIgnoreCase(scope)){if(!SCOPEREMOTE。equalsIgnoreCase(scope)){injvm协议的服务初始化exportLocal(url);}if(!SCOPELOCAL。equalsIgnoreCase(scope)){servicediscoverregistry协议的服务初始化urlexportRemote(url,registryURLs);if(!isGeneric(generic)!getScopeModel()。isInternal()){接口元数据信息发布MetadataUtils。publishServiceDefinition(url,providerModel。getServiceModel(),getApplicationModel());}}}this。urls。add(url);}orgapachedubboconfigServiceConfigprivatevoidexportLocal(URLurl){URLlocalURLBuilder。from(url)。setProtocol(LOCALPROTOCOL)设置协议为:injvm。setHost(LOCALHOSTVALUE)host:127。0。0。1。setPort(0)。build();locallocal。setScopeModel(getScopeModel())。setServiceModel(providerModel);doExportUrl(local,false);暴露本地服务。。。。。。} 初始化到本地的核心作用:创建InjvmProtocol对象将其放入到exporterMap中创建ListenerExporterWrapper对象,将InjvmProtocol对象将封装进去,然后ListenerExporterWrapper对象存入到exportersList中 servicediscoveryregistry协议服务初始化 注册servicediscoveryregistry协议的核心目的是为了注册与服务相关的元数据,默认情况下元数据通过InMemoryWritableMetadataService将数据存储在本地内存和本地文件。 核心代码在ServiceConfigexportRemote中,具体如下:注册servicediscoveryregistry协议的入口orgapachedubboconfigServiceConfigprivateURLexportRemote(URLurl,ListURLregistryURLs){if(CollectionUtils。isNotEmpty(registryURLs)){如果是多个注册中心,通过循环对每个注册中心进行注册for(URLregistryURL:registryURLs){一系列封装url逻辑,如:将servicenamemappingtrue加入到url中ifprotocolisonlyinjvm,notregister创建monitorURL,将其加入url中(如果没有配置monitor,则为null)Forproviders,thisisusedtoenablecustomproxytogenerateinvoker。。。。。。核心逻辑:注册servicediscoveryregistry协议复用服务暴露流程doExportUrl(registryURL。putAttribute(EXPORTKEY,url),true);}。。。。。。}else{。。。。。。}returnurl;} 将invoker包装成exporter,然后放入到exportersorgapachedubboconfigServiceConfigprivatevoiddoExportUrl(URLurl,booleanwithMetaData){为实现类创建Invoker,类型:JavassistProxyFactoryInvokerlt;?invokerproxyFactory。getInvoker(ref,(Class)interfaceClass,url);if(withMetaData){将invoker包装成DelegateProviderMetaDataInvokerinvokernewDelegateProviderMetaDataInvoker(invoker,this);}protocolSPI:协议自定义适配器,根据协议动态加载实现类此时exporter为ProtocolSerializationWrapper对象Exporterlt;?exporterprotocolSPI。export(invoker);将导出服务添加到exporters中exporters。add(exporter);} 通过RegistryProtocol将Invoker转化成Exporter 核心代码在ProtocolListenerWrapperexport中,具体如下:org。apache。dubbo。rpc。protocol。ProtocolListenerWrapperOverridepublicTExporterTexport(InvokerTinvoker)throwsRpcException{此时protocol为RegistryProtocol类型if(UrlUtils。isRegistry(invoker。getUrl())){returnprotocol。export(invoker);}。。。。。。} RegistryProtocol将Invoker转化成Exporter的核心流程 核心代码在RegistryProtocolexport中,具体如下:org。apache。dubbo。registry。integration。RegistryProtocolOverridepublicTExporterTexport(finalInvokerToriginInvoker)throwsRpcException{URLregistryUrlgetRegistryUrl(originInvoker);URLproviderUrlgetProviderUrl(originInvoker);。。。。。。创建ProviderConfigurationListener对象,缓存到本地registeredBeanInfoslist中MapURL,NotifyListeneroverrideListenersgetProviderConfigurationListener(providerUrl)。getOverrideListeners();将overrideSubscribeListener(默认为空)放入到overrideListenersmap中,overrideListeners。put(registryUrl,overrideSubscribeListener);从配置中心拉取数据,覆盖providerUrl属性providerUrloverrideUrlWithConfig(providerUrl,overrideSubscribeListener);暴露Triple协议的服务finalExporterChangeableWrapperTexporterdoLocalExport(originInvoker,providerUrl);registryUrl中包含servicediscoveryregistry协议通过该协议创建ServiceDiscoveryRegistry对象最后包装成ListenerRegistryWrapper对象finalRegistryregistrygetRegistry(registryUrl);finalURLregisteredProviderUrlgetUrlToRegistry(providerUrl,registryUrl);booleanregisterproviderUrl。getParameter(REGISTERKEY,true)registryUrl。getParameter(REGISTERKEY,true);if(register){注册servicediscoveryregistry协议register(registry,registeredProviderUrl);}。。。。。。。触发RegistryServiceListener的onRegister事件notifyExport(exporter);。。。。。。。} 步骤:getRegistryUrl:创建servicediscoveryregister协议URLgetProviderUrl:创建triple协议URLgetProviderConfigurationListener:1。初始化ProviderConfigurationListener类型监听器,将ProviderConfigurationListener对象缓存到registeredBeanInfoslist中。2。ProviderConfigurationListener从Nacos配置中心拉取ProviderConfiguration相关的配置,供Provider使用。(注:如果有需要,可以在dataId:annotationuser。configurators,group:dubbo配置文件中自定义一些provider相关的配置)overrideUrlWithConfig:1。创建serviceConfigurationListener,将serviceConfigurationListener缓存到serviceConfigurationListenersmap中。2。serviceConfigurationListener从nacos拉取服务配置信息(入参:dataId:。UserApi:version:。configurators,group:dubbo)。根据Nacos配置覆盖providerUrl配置doLocalExport:暴露Triple协议的服务。1。创建ExporterChangeableWrapper对象,将其放入到boundsmap中。2。创建ListenerExporterWrapper对象将其封装到ExporterChangeableWrapper对象中,将TripleProtocol对象封装到ListenerExporterWrapper对象中getRegistry:registryUrl是servicediscoveryregistry协议URL。1。创建协议对应的ServiceDiscoveryRegistry对象,将其放入registriesmap中。2。创建RegistryFactoryWrapper对象最终会创建ListenerRegistryWrapper对象,同时从Nacos配置中心拉取配置信息,进行数据封装 注册Triple协议的服务 核心代码在RegistryProtocoldoLocalExport中,具体如下:org。apache。dubbo。registry。integration。RegistryProtocolprivateTExporterChangeableWrapperTdoLocalExport(finalInvokerToriginInvoker,URLproviderUrl){StringkeygetCacheKey(originInvoker);return(ExporterChangeableWrapperT)bounds。computeIfAbsent(key,s{Invokerlt;?invokerDelegatenewInvokerDelegate(originInvoker,providerUrl);最终会调用TripleProtocolexport(InvokerTinvoker)方法returnnewExporterChangeableWrapper((ExporterT)protocol。export(invokerDelegate),originInvoker);});}org。apache。dubbo。rpc。protocol。tri。TripleProtocolpublicTExporterTexport(InvokerTinvoker)throwsRpcException{。。。。。。将tri协议缓存到exporterMap中(如:keyai。advance。guardian。annotation。api。SysUserApi:1。0。0:30002)exporter类型为TripleProtocolexporterMap。put(key,exporter);将invoker放入到invokersSet中,invoker类型为:FilterChainBuilder。CallbackRegistrationInvokerinvokers。add(invoker);将invoker放入到pathResolver中(如:keyai。advance。guardian。annotation。api。SysUserApi:1。0。0)pathResolver类型为TriplePathResolver,初始化TripleProtocol时通过SPI创建pathResolverpathResolver。add(url。getServiceKey(),invoker);keyai。advance。guardian。annotation。api。SysUserApipathResolver。add(url。getServiceModel()。getServiceModel()。getInterfaceName(),invoker);。。。。。。url。getOrDefaultApplicationModel()。getExtensionLoader(ExecutorRepository。class)。getDefaultExtension()通过SPI获取ExecutorRepository。createExecutorIfAbsent(url);给接口创建执行线程池,然后放入到executorsmap中PortUnificationExchanger。bind(url);optimizeSerialization(url);returnexporter;} 注:protocol为ProtocolSerializationWrapper类型对象,逻辑跟injvm协议的服务注册基本一致,不过暴露Triple协议的服务最终会创建TripleProtocol对象TripleProtocol中将export、invoker等放入map中 注册servicediscoveryregistry协议 核心代码在ServiceDiscoveryRegistryregister和MetadataInfoaddService中,具体如下:org。apache。dubbo。registry。client。ServiceDiscoveryRegistrypublicfinalvoidregister(URLurl){if(!shouldRegister(url)){ShouldNotRegisterreturn;}注册servicediscoveryregistry协议doRegister(url);}org。apache。dubbo。metadata。MetadataInfopublicsynchronizedvoidaddService(URLurl){fixme,passinapplicationmodecontextduringinitializationofMetadataInfo。if(this。loadernull){this。loaderurl。getOrDefaultApplicationModel()。getExtensionLoader(MetadataParamsFilter。class);}ListMetadataParamsFilterfiltersloader。getActivateExtension(url,paramsfilter);generateservicelevelmetadataServiceInfoserviceInfonewServiceInfo(url,filters);将serviceInfo放入到servicesmap中this。services。put(serviceInfo。getMatchKey(),serviceInfo);extractcommoninstancelevelparamsextractInstanceParams(url,filters);if(exportedServiceURLsnull){exportedServiceURLsnewConcurrentSkipListMap();}将url放入到exportedServiceURLsmap中addURL(exportedServiceURLs,url);updatedtrue;} 注: ServiceDiscoveryRegistrydoRegister最终会调用到MetadataInfoaddService 缓存servicediscoveryregistry协议URL到exportedServiceURLsmap中 缓存serviceInfo到servicesmap中 发布服务注册事件 核心代码在RegistryProtocolnotifyExport中,具体如下:org。apache。dubbo。registry。integration。RegistryProtocolprivateTvoidnotifyExport(ExporterChangeableWrapperTexporter){ScopeModelscopeModelexporter。getRegisterUrl()。getScopeModel();ListRegistryProtocolListenerlistenersScopeModelUtil。getExtensionLoader(RegistryProtocolListener。class,scopeModel)。getActivateExtension(exporter。getOriginInvoker()。getUrl(),REGISTRYPROTOCOLLISTENERKEY);if(CollectionUtils。isNotEmpty(listeners)){for(RegistryProtocolListenerlistener:listeners){发布RegistryProtocolListener的onExport事件listener。onExport(this,exporter);}}} 注:我们可以看出注册servicediscoveryregistry协议的核心目的是为了将服务的接口相关的信息存储在内存中。从兼容性和平滑迁移两方面来考虑,在实现的时候采取复用ServiceConfig的暴露流程的方式。通过SPI方式动态创建RegistryProtocolListener的实现类是MigrationRuleListener,构造函数中会调用Nacos配置中心配置信息(订阅规则信息),如果应用中有订阅者则可以规则改变,进而概念调用者的行为。 triple协议服务初始化 由于暴露Triple协议服务的流程和暴露Injvm协议服务的流程是一致的,所以不再赘述。 协议的服务初始化总结: 代码中大量运用了DubboSPI方式动态加载实现类,相比JavaSPI,DubboSPI加载实现类更加灵活,不过代码没有运行起来的情况下可读性稍差协议的服务初始化主要目的是初始化注册、服务相关的元数据,以供元数据发布、服务注册时使用,不过也可以从Nacos配置中心读取数据,进行初始化 应用级服务注册发布 服务注册发布包含3部分:接口元数据发布应用映射接口数据发布应用服务注册 接口元数据发布 元数据分两种类型:注册中心的应用实例中的元数据信息(protocol、version相关信息)配置中心的接口相关的元数据信息(接口、方法、host、port、version等等相关信息) 此处将接口元数据发布到配置中心 因为在前面完成了injvm、servicediscoveryregistry、tri等等协议服务初始化工作之后基本完成了MetaData、ServiceBean的初始化工作,所以接下来的口元数据发布到配置中心比较简单 核心代码从MetadataUtilspublishServiceDefinition开始,具体如下:org。apache。dubbo。registry。client。metadata。MetadataUtilspublicstaticvoidpublishServiceDefinition(URLurl,ServiceDescriptorserviceDescriptor,ApplicationModelapplicationModel){。。。。。。Stringsideurl。getSide();if(PROVIDERSIDE。equalsIgnoreCase(side)){StringserviceKeyurl。getServiceKey();FullServiceDefinitionserviceDefinitionserviceDescriptor。getFullServiceDefinition(serviceKey);if(StringUtils。isNotEmpty(serviceKey)serviceDefinition!null){serviceDefinition。setParameters(url。getParameters());for(Map。EntryString,MetadataReportentry:getMetadataReports(applicationModel)。entrySet()){MetadataReportmetadataReportentry。getValue();if(!metadataReport。shouldReportDefinition()){logger。info(Reportofservicedefinitionisdisabledforentry。getKey());continue;}metadataReport。storeProviderMetadata(newMetadataIdentifier(url。getServiceInterface(),url。getVersion()null?:url。getVersion(),url。getGroup()null?:url。getGroup(),PROVIDERSIDE,applicationModel。getApplicationName()),serviceDefinition);}}}。。。。。。}orgapachedubbometadatareportsupportAbstractMetadataReportprivatevoidstoreProviderMetadataTask(MetadataIdentifierproviderMetadataIdentifier,ServiceDefinitionserviceDefinition){try{。。。。。。allMetadataReports。put(providerMetadataIdentifier,serviceDefinition);failedReports。remove(providerMetadataIdentifier);StringdataJsonUtils。getJson()。toJson(serviceDefinition);doStoreProviderMetadata(providerMetadataIdentifier,data);saveProperties(providerMetadataIdentifier,data,true,!syncReport);}catch(Exceptione){。。。。。。}} 步骤:publishServiceDefinition:getMetadataReports(applicationModel)获取接口元数据信息(该接口元数据通过DefaultApplicationDeployerstartMetadataCenter方法加载,其实在DeubboConfigBean初始化阶段已完成MetadataCenter数据初始化)。storeProviderMetadata:去发布接口的元数据。1。storeProviderMetadataTask:将serviceDefinition放入到mapallMetadataReports中。(以providerMetadataIdentifier作为key)生成元数据json字符串data)。2。storeMetadata:开始发布元数据信息(拼接生成dataId,拼接方法:{serviceInterface}:{version}:{group}:{side}:{applicationName},如:。。。UserApi:1。0。0::provider:applicationname。注:初始化MetadataIdentifier时,group为空)。publishConfigInner:调用nacosclient接口发布元数据信息,在nacos配置列表,指定namespace、指定group下创建配置数据saveProperties:将元数据信息放入到properties中,既本地缓存doSaveProperties:将元数据存储到本地文件中,文件为:Usersadvance。dubbodubbometadataapplicationname。。。8848。cache 应用映射接口数据发布 因为在前面已经完成了MetaData、ServiceBean的初始化工作的初始化工作,所以到这里,应用映射接口数据发布过程其实比较简单 数据格式如下:{dataId:。。。UserApi,group:mapping,content:applicationname} 从ServiceConfigexported开启应用映射接口数据发布,以下是一些核心逻辑调用来源为ServiceConfig。doExport()org。apache。dubbo。config。spring。ServiceBeanprotectedvoidexported(){调用ServiceConfig。exported方法super。exported();PublishServiceBeanExportedEventpublishExportEvent();}orgapachedubboconfigServiceConfigprotectedvoidexported(){exportedtrue;ListURLexportedURLsthis。getExportedUrls();exportedURLs。forEach(url{if(url。getParameters()。containsKey(SERVICENAMEMAPPINGKEY)){通过SPI方式动态获取MetadataServiceNameMapping对象ServiceNameMappingserviceNameMappingServiceNameMapping。getDefaultExtension(getScopeModel());try{封装应用映射接口数据booleansucceededserviceNameMapping。map(url);。。。。。。}catch(Exceptione){logger。error(Failedregisterinterfaceapplicationmappingforserviceurl。getServiceKey(),e);}}});。。。。。。}org。apache。dubbo。metadata。store。nacos。NacosMetadataReportOverridepublicbooleanregisterServiceAppMapping(Stringkey,Stringgroup,Stringcontent,Objectticket){try{if(!(ticketinstanceofString)){thrownewIllegalArgumentException(nacospublishConfigCasrequiresstringtypeticket);}往nacos配置中心注册应用映射数据returnconfigService。publishConfigCas(key,group,content,(String)ticket);}catch(NacosExceptione){logger。warn(nacospublishConfigCasfailed。,e);returnfalse;}}org。apache。dubbo。metadata。store。nacos。NacosConfigServiceWrapperpublicbooleanpublishConfigCas(StringdataId,Stringgroup,Stringcontent,StringcasMd5)throwsNacosException{调用nacos接口往配置中心注册应用映射数据returnconfigService。publishConfigCas(handleInnerSymbol(dataId),handleInnerSymbol(group),content,casMd5);} exported:应用映射接口数据。1。获取到已初始化的exportedURLs。2。serviceNameMapping。map最终会调用到NacosMetadataReportregisterServiceAppMapping方法(往配置中心发布应用映射接口数据)publishExportEvent:发布导出应用事件 应用级服务注册 注册应用级服务是Dubbo3。0服务暴露的核心流程,也就是之前提到的应用级服务发现机制。 相比接口元数据的发布和应用映射接口数据的发布,应用级服务注册放在了最后执行 ServiceInstance封装 应用级服务的注册从DefaultApplicationDeployerprepareApplicationInstance开始,以下是一些核心逻辑:org。apache。dubbo。config。deploy。DefaultApplicationDeployerpublicvoidprepareApplicationInstance(){。。。。。。if(isRegisterConsumerInstance()){exportMetadataService();if(hasPreparedApplicationInstance。compareAndSet(false,true)){最终会调用到AbstractServiceDiscoveryregister()registerServiceInstance();}}}org。apache。dubbo。registry。clien。AbstractServiceDiscoverypublicsynchronizedvoidregister()throwsRuntimeException{创建服务实例对象this。serviceInstancecreateServiceInstance(this。metadataInfo);if(!isValidInstance(this。serviceInstance)){return;}创建元数据版本号booleanrevisionUpdatedcalOrUpdateInstanceRevision(this。serviceInstance);if(revisionUpdated){。。。。。。doRegister(this。serviceInstance);应用注册}}protectedServiceInstancecreateServiceInstance(MetadataInfometadataInfo){DefaultServiceInstanceinstancenewDefaultServiceInstance(serviceName,applicationModel);instance。setServiceMetadata(metadataInfo);设置dubbo。metadata。storagetype:localremote,local:存储在应用中,remote:存储在配置中心setMetadataStorageType(instance,metadataType);封装元数据,例如:timestamp1662343919223ServiceInstanceMetadataUtils。customizeInstance(instance,applicationModel);returninstance;} 注: 从DefaultModuleDeployerstart()的onModuleStarted()调用开始应用服务注册 步骤:prepareApplicationInstance:开始应用注册registerMetadataAndInstance:1。从BeanFactory获取registryManager,获取ServiceDiscoveries。2。调用serviceDiscovery。register方法,开始注册createServiceInstance:1。创建DefaultServiceInstance对象。2。设置元数据(如:dubbo。metadata。storagetype:localremote,local:存储在应用中,remote:存储在配置中心)calOrUpdateInstanceRevision:创建元数据版本号reportMetadata:判断是否需要将元数据发布到配置中心 向Nacos注册dubbo应用org。apache。dubbo。registry。nacos。NacosServiceDiscoveryOverridepublicvoiddoRegister(ServiceInstanceserviceInstance){execute(namingService,service{InstanceinstancetoInstance(serviceInstance);service。registerInstance(instance。getServiceName(),Constants。DEFAULTGROUP,instance);});}publicvoidregisterInstance(StringserviceName,Stringgroup,Instanceinstance)throwsNacosException{namingService。registerInstance(handleInnerSymbol(serviceName),group,instance);} 步骤:doRegister:异步向注册dubbo应用将DefaultServiceInstanceserviceInstance封装成Instanceinstance对象,形成最终的应用注册信息,如下所示。registerInstance:dubbo应用group默认为:DEFAULTGROUP 最终注册的dubbo应用信息 Dubbo应用注册和SpringBoot应用注册区别Instance中metadata(Dubbo有5项数据、SpringBoot只有一项数据)SpringBoot注册group可以自定义,Dubbo不能自定义,默认为DEFAULTGROUP应用注册触发机制不同 总结 通过对Dubbo3。0服务端暴露全流程的解析发现,应用级服务发现机制的实现相对复杂,本文时序图以及代码梳理仅仅梳理核心又关键的逻辑点,想要深入理解Dubbo3。0应用级服务注册,还需要自己动手深入了解 最后由于本人能力有限,如果有错误的地方,请指出!