范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

Dubbo3。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中开始 ,整体链路如下:
  具体如下:// org.apache.dubbo.config.spring.context.DubboDeployApplicationListener @Override public 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/ServiceConfig protected synchronized void doExport() {     ......     doExportUrls(); // 协议的服务初始化,     exported();  // 应用级服务注册、元数据发布 }
  协议服务初始化、元数据封装、服务注册/发布的整体入口从监听Spring上下文刷新事件开始 注:onApplicationEvent是一个Spring上下文刷新事件的通知类,DubboDeployApplicationListener类实现了ApplicationListener,通过一个事件监听器开始dubbo服务注册onContextRefreshedEvent最终会调用到ServiceConfig#doExportdoExportUrls:协议的服务初始化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/ServiceConfig private void exportUrl(URL url, List 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/ServiceConfig private 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);  //暴露本地服务     ...... }
  初始化到本地的核心作用:创建InjvmProtocol对象将其放入到exporterMap中创建ListenerExporterWrapper对象,将InjvmProtocol对象将封装进去,然后ListenerExporterWrapper对象存入到exporters List中
  service-discovery-registry协议服务初始化
  注册 service-discovery-registry 协议的核心目的是为了注册与服务相关的元数据,默认情况下元数据通过 InMemoryWritableMetadataService 将数据存储在本地内存和本地文件。
  核心代码在ServiceConfig#exportRemote 中,具体如下:注册 service-discovery-registry 协议的入口//org/apache/dubbo/config/ServiceConfig private URL exportRemote(URL url, List 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/ServiceConfig private 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 @Override public  Exporter export(Invoker 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 @Override public  Exporter export(final Invoker originInvoker) throws RpcException {     URL registryUrl = getRegistryUrl(originInvoker);     URL providerUrl = getProviderUrl(originInvoker);     ......     //创建ProviderConfigurationListener对象,缓存到本地registeredBeanInfos list中     Map overrideListeners = getProviderConfigurationListener(providerUrl).getOverrideListeners();     // 将overrideSubscribeListener(默认为空)放入到overrideListeners map中,     overrideListeners.put(registryUrl, overrideSubscribeListener);     // 从配置中心拉取数据,覆盖providerUrl属性     providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);     //暴露Triple协议的服务     final ExporterChangeableWrapper 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);     ....... }
  步骤:getRegistryUrl:创建service-discovery-register协议URLgetProviderUrl:创建triple协议URLgetProviderConfigurationListener:1. 初始化ProviderConfigurationListener类型监听器,将ProviderConfigurationListener对象缓存到registeredBeanInfos list中。2. ProviderConfigurationListener从Nacos配置中心拉取ProviderConfiguration相关的配置,供Provider使用。(注:如果有需要,可以在dataId:annotation-user.configurators,group:dubbo 配置文件中自定义一些provider相关的配置)overrideUrlWithConfig:1. 创建serviceConfigurationListener,将serviceConfigurationListener缓存到serviceConfigurationListeners map中。2. serviceConfigurationListener从nacos拉取服务配置信息(入参:dataId:******.UserApi:version:.configurators,group:dubbo)。根据Nacos配置覆盖providerUrl配置doLocalExport:暴露Triple协议的服务。1. 创建ExporterChangeableWrapper对象,将其放入到bounds map中。2. 创建ListenerExporterWrapper对象将其封装到ExporterChangeableWrapper对象中,将TripleProtocol对象封装到ListenerExporterWrapper对象中getRegistry:registryUrl是service-discovery-registry协议URL。1. 创建协议对应的ServiceDiscoveryRegistry对象,将其放入registries map中。2. 创建RegistryFactoryWrapper对象最终会创建ListenerRegistryWrapper对象,同时从Nacos配置中心拉取配置信息,进行数据封装
  注册Triple 协议的服务
  核心代码在 RegistryProtocol#doLocalExport 中,具体如下: // org.apache.dubbo.registry.integration.RegistryProtocol private  ExporterChangeableWrapper doLocalExport(final Invoker originInvoker, URL providerUrl) {     String key = getCacheKey(originInvoker);     return (ExporterChangeableWrapper) bounds.computeIfAbsent(key, s -> {         Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);         // 最终会调用TripleProtocol#export(Invoker invoker)方法         return new ExporterChangeableWrapper<>((Exporter) protocol.export(invokerDelegate), originInvoker);     }); } // org.apache.dubbo.rpc.protocol.tri.TripleProtocol public  Exporter export(Invoker 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; }
  注:protocol为ProtocolSerializationWrapper类型对象,逻辑跟injvm协议的服务注册基本一致,不过暴露Triple协议的服务最终会创建TripleProtocol对象TripleProtocol中将export、invoker等放入map中
  注册service-discovery-registry协议
  核心代码在 ServiceDiscoveryRegistry#register和MetadataInfo#addService 中,具体如下:// org.apache.dubbo.registry.client.ServiceDiscoveryRegistry public final void register(URL url) {     if (!shouldRegister(url)) { // Should Not Register         return;     }     // 注册service-discovery-registry协议     doRegister(url); } // org.apache.dubbo.metadata.MetadataInfo public 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 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.RegistryProtocol private  void notifyExport(ExporterChangeableWrapper exporter) {     ScopeModel scopeModel = exporter.getRegisterUrl().getScopeModel();     List 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);         }     } }
  注:我们可以看出注册 service-discovery-registry 协议的核心目的是为了将服务的接口相关的信息存储在内存中。从兼容性和平滑迁移两方面来考虑,在实现的时候采取复用 ServiceConfig 的暴露流程的方式。通过SPI方式动态创建RegistryProtocolListener的实现类是MigrationRuleListener,构造函数中会调用Nacos配置中心配置信息(订阅规则信息),如果应用中有订阅者则可以规则改变,进而概念调用者的行为。
  triple协议服务初始化
  由于暴露 Triple 协议服务的流程和暴露 Injvm 协议服务的流程是一致的,所以不再赘述。
  协议的服务初始化总结:
  代码中大量运用了Dubbo SPI方式动态加载实现类,相比Java SPI,Dubbo SPI加载实现类更加灵活,不过代码没有运行起来的情况下可读性稍差协议的服务初始化主要目的是初始化注册、服务相关的元数据,以供元数据发布、服务注册时使用,不过也可以从Nacos配置中心读取数据,进行初始化
  应用级服务注册/发布
  服务注册/发布包含3部分:接口元数据发布应用映射接口数据发布应用服务注册
  接口元数据发布
  元数据分两种类型:注册中心的应用实例中的元数据信息(protocol、version相关信息)配置中心的接口相关的元数据信息(接口、方法、host、port、version等等相关信息)
  此处将接口元数据发布到配置中心
  因为在前面完成了injvm、service-discovery-registry、tri等等协议服务初始化工作之后基本完成了MetaData、ServiceBean的初始化工作,所以接下来的口元数据发布到配置中心比较简单
  核心代码从MetadataUtils#publishServiceDefinition开始,具体如下: // org.apache.dubbo.registry.client.metadata.MetadataUtils public 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 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/AbstractMetadataReport private 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) {         ......     } }
  步骤:publishServiceDefinition:getMetadataReports(applicationModel)获取接口元数据信息(该接口元数据通过DefaultApplicationDeployer#startMetadataCenter方法加载,其实在DeubboConfigBean初始化阶段已完成MetadataCenter数据初始化 )。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为空)。publishConfigInner:调用nacos-client接口发布元数据信息,在nacos配置列表,指定namespace、指定group下创建配置数据saveProperties:将元数据信息放入到properties中,既本地缓存doSaveProperties:将元数据存储到本地文件中,文件为:/Users/advance/.dubbo/dubbo-metadata-application-name-×××.×××.×××.×××-8848.cache
  应用映射接口数据发布
  因为在前面已经完成了MetaData、ServiceBean的初始化工作的初始化工作,所以到这里,应用映射接口数据发布过程其实比较简单
  数据格式如下:{     "dataId":"×××.×××.×××.UserApi",     "group":"mapping",     "content":"application-name" }
  从ServiceConfig#exported开启"应用映射接口数据"发布,以下是一些核心逻辑// 调用来源为ServiceConfig.doExport() // org.apache.dubbo.config.spring.ServiceBean protected void exported() {     // 调用ServiceConfig.exported方法     super.exported();     // Publish ServiceBeanExportedEvent     publishExportEvent(); } // org/apache/dubbo/config/ServiceConfig protected void exported() {     exported = true;     List 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 @Override public 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.NacosConfigServiceWrapper public boolean publishConfigCas(String dataId, String group, String content, String casMd5) throws NacosException {     //调用nacos接口往配置中心注册应用映射数据     return configService.publishConfigCas(handleInnerSymbol(dataId), handleInnerSymbol(group), content, casMd5); }
  exported:应用映射接口数据。1. 获取到已初始化的exportedURLs。2. serviceNameMapping.map最终会调用到NacosMetadataReport#registerServiceAppMapping方法(往配置中心发布应用映射接口数据)publishExportEvent:发布导出应用事件
  应用级服务注册
  注册应用级服务是Dubbo3.0服务暴露的核心流程,也就是之前提到的应用级服务发现机制。
  相比接口元数据的发布和应用映射接口数据的发布,应用级服务注册放在了最后执行
  ServiceInstance封装
  应用级服务的注册从DefaultApplicationDeployer#prepareApplicationInstance开始,以下是一些核心逻辑:// org.apache.dubbo.config.deploy.DefaultApplicationDeployer public void prepareApplicationInstance() {     ......     if (isRegisterConsumerInstance()) {         exportMetadataService();         if (hasPreparedApplicationInstance.compareAndSet(false, true)) {             // 最终会调用到AbstractServiceDiscovery#register()             registerServiceInstance();         }     } } // org.apache.dubbo.registry.clien.AbstractServiceDiscovery public 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()调用开始应用服务注册
  步骤:prepareApplicationInstance:开始应用注册registerMetadataAndInstance:1. 从BeanFactory获取registryManager,获取ServiceDiscoveries。2. 调用serviceDiscovery.register方法,开始注册createServiceInstance:1. 创建DefaultServiceInstance对象。2. 设置元数据(如:dubbo.metadata.storage-type:local/remote,local:存储在应用中,remote:存储在配置中心)calOrUpdateInstanceRevision:创建元数据版本号reportMetadata: 判断是否需要将元数据发布到配置中心
  向Nacos注册dubbo应用// org.apache.dubbo.registry.nacos.NacosServiceDiscovery @Override public 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); }
  步骤:doRegister:异步向注册dubbo应用将DefaultServiceInstance serviceInstance封装成Instance instance对象,形成最终的应用注册信息,如下所示。registerInstance:dubbo应用group默认为:DEFAULT_GROUP
  最终注册的dubbo应用信息
  Dubbo应用注册和Spring Boot应用注册区别Instance中metadata(Dubbo有5项数据、Spring Boot只有一项数据)Spring Boot注册group可以自定义,Dubbo不能自定义,默认为DEFAULT_GROUP应用注册触发机制不同
  总结
  通过对Dubbo 3.0服务端暴露全流程的解析发现,应用级服务发现机制的实现相对复杂,本文时序图以及代码梳理仅仅梳理核心又关键的逻辑点,想要深入理解Dubbo3.0应用级服务注册,还需要自己动手深入了解
  最后由于本人能力有限,如果有错误的地方,请指出!

晒幸福!东沟馍馍产业步入发展快车道央视网消息青海省海东市互助土族自治县是全国唯一的土族自治县。春节临近,这里年味儿渐浓,家家户户都会忙活起来,炸年馍迎新春。土族年馍分为焜炸蒸烙四大系列共20多个品种。一团发好的面团红旗正式发布新能源品牌,新车续航达1000km,将在2023年内推出1月8日,2023年中国一汽红旗品牌新能源汽车全球战略发布会在广州盛大召开。在此次发布会上,红旗正式发布了其新能源品牌和旗妙双翼的全新LOGO,同时,还公开了全新的设计语言新能源技三亚旅游平替大涨价!400元房费变2000新的穷游城市被翻出在三亚火出圈之后,春节旅游市场就连三亚平替目的地也变得不再平替。1月16日,记者发现,春节期间,此前作为三亚平替目的地的西双版纳万宁陵水等地酒店价格上涨,部分酒店房价上涨近4倍,部盐城大纵湖红红火火闹新春现代快报讯(通讯员陈浩浩记者王菲)来大纵湖,过宋潮年。这个春节,盐城大纵湖旅游度假区结合滋味盐城过大年主题,开展宋文化生态文化民俗文化相结合的节庆活动,打造吃住行游购娱特色体验游玩北京密云北京山水大观京郊最美的郊野公园区名由来密云地处北京市东北部燕山山脉脚下,历史悠久,既是全国农业生态试点县,又是全国绿化先进县,国家生态县,被誉为北京山水大观,首都郊野公园,是华北通往东北内蒙古的重要门户,故有京去了这座云南最仙小城,才知道什么叫适合发呆!图虫子晋的时光碎片提起中国最多彩的省份,必定绕不开云南。而说到彩云之南,很多人会想起昆明大理丽江,香格里拉泸沽湖这些美丽的地方。但很多人可能不知道的是,在距离昆明仅1h的地方,还有不止云南!在甘孜也能开启刘亦菲同款治愈之旅!要说最近什么剧最火,那必须是由刘亦菲李现主演的去有风的地方,剧里温馨的小院蔚蓝的天空灿烂的暖阳,每一帧定格的画面都无比治愈,仿佛能让人放下三千烦恼。而在甘孜,其实也能找到神仙姐姐同1973年,我们进藏新兵,与都兰兵站的炊事班干架了回眸参军五十载雪域浪人点击进入第一篇崇敬军人,我要去当兵点击进入第二篇双流中和镇,新兵集训忙第三篇神秘的青藏高原号称世界屋脊的青藏高原,历来披着一层神秘的面纱为世人所瞩目。在这块谜非遗的浪漫南县夜空绽放火树银花满天星红网时刻益阳1月17日讯(通讯员彭辉虎王征洋)春节临近,人气回升,各大商场开启新春模式,相互借力吸引人气。近日,在南县桂花广场,4名非遗打铁花匠人为当地群众和游客带来了美轮美奂的铁如果你是这4个姓氏之一,可能不是炎黄子孙,而是上古蚩尤的后裔近年来,文化传承成为炙手可热的名词,随着一阵历史热,小众的历史考古博物馆等专业也渐渐走进人们的视野。众多史料文献出土,也拓宽了了人们的固有认识,除了炎黄以外,蚩尤的传说也成为了常被听说这个品牌很有名?Ubiquiti优倍快USBC充电器拆解前言充电头网拿到了Ubiquiti优倍快一款USBC充电器,同样也是白色外观设计。这款充电器为固定美规插脚设计,自带1。5米长输出线缆,只支持5V3A输出,看起来是某种产品的配机充
3年复合增长率112,谁说年轻人不爱吃辣条了?红网时刻新闻报道通讯员常红辣条第一股来了。在卫龙即将上市的时刻,招股书显示,其半年亏损2。61亿元,紧接着,年轻人不爱吃辣条了吗的话题被推上微博热搜,引发热议。一不小心又被代表的年机械表长时间不戴会坏吗?手表我们很多玩友应该都有一块吧。而且我们文玩圈内还有一部分收藏钟表的爱好者们。今天在网上看到很多玩友们在讨论机械表长时间不戴会不会坏?的这个话题。小编觉得很有具代表性以及意义,相信时光不老,爱你不变,写给最爱的人时光不老,爱你不变,写给最爱的人在偌大个世界里,能够遇见你,是我最幸运的事,能够与你执手相牵奔走在人生的轨道上,是我最幸福的事。可惜,幸福是那么的短暂,短到如昙花一现,思念却是那么科学强国猪马羊正在表达什么情绪?人类可以通过其声音感受到近日,有研究人员发现,人类能够根据猪马和山羊等动物的声音来了解它们的感受。从羊发出的声音中可以分辨出情绪表达英国卫报报道,该研究团队表示,这些发现表明声音中的某些信息似乎在不同物种地狱犬来了?后真相时代,让真相多飞一会儿陈文瑀(重庆大学)近日网络流传,一种名为BQ。1。1的新型变异毒株已经出现,该毒株的传染性和致死率都很高,又被称作地狱犬。实际的情况是这样的BQ。1是奥密克戎BA。5在人群流行传播风吹半夏千禧年的到来,半夏童骁骑离心,小苏的时代要来了千禧年的到来,是新世纪的开始,也是许半夏的开始,童骁骑跟高辛夷举办了世纪婚礼,赵垒向半夏求了婚,一切都是美好的开始,但只是开始,一切都还尘埃未定。在童骁骑的婚礼上,高辛夷想把捧花抛世界三大公认足球球王之一,改变巴西足球时代的人,贝利的一生巴西里约,一个被称为上帝之城的一个贫民区里,曾经有一个很喜欢足球的男孩,他的父亲是位因伤退役后来穷困潦倒的前职业足球运动员。家里穷的叮当响,根本没钱买足球,但为了鼓励和支持儿子对足泰国庆祝今年迎来1000万旅客人次,总理巴育迎接外国游客12月10日,泰国在七个机场和两个移民关卡举行欢迎外国游客的庆祝活动,以纪念今年入境泰国的外国旅客达到1000万人次。据泰国曼谷邮报12月10日报道,泰国旅游与体育部组织了这场名为李嘉欣携富商老公聚会,穿露脐装配皮裙富贵气逼人,儿子颜值拉垮天气慢慢转凉,可是对于爱美的女性来讲,那可没有季节之分,依旧还是会选择要风度不要温度的打扮,但这出现在年轻小姐姐身上,一些大龄女性还是比较少见的,因为年龄不允许她们这样毫无顾忌的凹王者荣耀实战峡谷推推乐把对面心态干爆炸我们的张飞被海月拉进小黑屋了,我们又进不去,现在只能等了。差不多快要出来了,来接应!一手大招推三个,有两个残血,瞄准他们一技能劈两个,差点双杀,有点可惜。我们这个阵容推人强,推塔真肠胃不好,听听专家推荐的养胃饮食法常见的养胃饮食误区有哪些?红茶并不养胃茶叶中的咖啡碱可以促进胃酸分泌,而红茶中的咖啡碱含量比绿茶还高,胃溃疡患者胃酸分泌较高,如果喝红茶特别是浓的红茶,胃酸分泌更多,胃可能更难受。