目录
DubboMetadataService代理类 创建时机及方式分析
解决方案
配置如下Bean类就好
@Component
public class DubboSubscribedServicesChangedEvent implements SmartInitializingSingleton {
@Autowired
private DubboServiceMetadataRepository dubboServiceMetadataRepository;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void afterSingletonsInstantiated() {
//最终目的是用于创建NamingEvent的监听器
dubboServiceMetadataRepository.initSubscribedServices();
//获取dubbo订购的服务
Set<String> subscribedServices = dubboServiceMetadataRepository.getSubscribedServices();
if (subscribedServices.contains("*")) {
subscribedServices = new HashSet<>(discoveryClient.getServices());
}
for (String serviceName : subscribedServices) {
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceName);
//必须得判空,否则这边通知服务变更成0实例,nacos那边又刚好注册一个实例,两边并行执行会出现问题
if(CollectionUtils.isNotEmpty(serviceInstances)){
//主动发送服务变更事件,触发创建DubboMetadataService的代理类
ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent(serviceName,
serviceInstances);
applicationEventPublisher.publishEvent(event);
}
}
}
}
前言
使用的相关版本:
dubbo:2.7.8
spring-cloud:2.2.6.RELEASE
spring-cloud-starter-dubbo:2.2.6.RELEASE
spring-boot:2.3.7.RELEASE
注册中心:Nacos1.3.1
使用的dubbo注册类:DubboCloudRegistry
由于涉及到的一些spring 启动过程一些扩展点的执行顺序,所以下面先给出相关扩展点的执行顺序图(只包含涉及到的扩展点)。对spring的启动过程很熟悉的可以直接跳过
原因分析
初步分析
根据日志可定位到错误是在 RegistryDirectory.doList 抛出的,代码如下所示
从上面代码中可以看出是当 forbidden=true 时就会抛出如上错误。
forbidden 赋值位置,代码如下图所示
forbiddern 是在 RegistryDirectory.refreshInvoker 中判断入参 invokerUrls 是否符合相应规则,符合为false,否则为true。
而RegistryDirectory.refreshInvoker的调用流程如下图所示
流程图中有两个关键点会影响到 RegistryDirectory.refreshInvoker 的入参 invokerUrls
- 服务消费方需要先获取到 DubboMetadataService代理类 。当 DubboMetadataService 不存在时RegistryDirectory.refreshInvoker 的入参 invokerUrls 无法符合相应规则,forbiddern 设为 true
- 而即使服务消费方拿到 DubboMetadataService代理类 后并发出请求时,如果服务提供方还未把URL 注册到 DubboServiceMetadataRepository.allExportUrls中,则传给RegistryDirectory.refreshInvoker 的入参 invokerUrls 依然无法法符合相应规则,forbiddern 设为 true。
总结
综上所述:能否把 RegistryDirectory.forbiddern 设为 true 的核心点在于:
1.能否正常获取到 DubboMetadataService代理类 ,这涉及到 DubboMetadataService代理类 创建时机及方式
2.服务提供方能否在消费方获取URL前先把本地服务注册到DubboServiceMetadataRepository.allExportUrls 中
DubboMetadataService代理类 创建时机及方式分析
DubboMetadataService代理类 的几种创建情况的执行流程如下图所示
如流程图所示, DubboMetadataService 代理类 有如下几种情况会触发创建
1.DubboCloudRegistry.preInit 的执行,DubboCloudRegistry.preInit 的核心逻辑有控制只能执行一次,所以多次执行只会触发一次 DubboMetadataService 代理类 的创建,DubboCloudRegistry.preInit 有如下两种情况会触发执行
①.当 spring容器 启动实例化 Dubbo服务Bean 时,触发 DubboCloudRegistry.preInit 执行
②.当 DubboBootstrapApplicationListener 监听到 ContextRefreshedEvent 事件时触发创建 DubboCloudRegistry.preInit 执行
2.有nacos注册中心发出的服务变更请求经过一系列的事件发布及执行后最终触发 DubboMetadataService 代理类 的创建
而基于上面流程图,当服务提供方和消费方的执行时序如下图所示时,就会导致 DubboMetadataService代理类 无法正常被创建
时序图中分六个阶段
- 第一阶段:Dubbo服务 Bean 的创建触发执行DubboCloudRegistry.preInit ,从而触发 DubboMetadataService 代理类 的创建,此时由于服务提供方还未把服务注册到 Nacos ,导致 DubboMetadataService 代理类 无法正常创建
- 第二阶段:要创建 NamingEvent 的监听器,但由于 DubboServiceDiscoveryAutoConfiguration.NacosConfiguration Bean 还未创建导致无法监听到 SubscribedsServiceChangedEvent 事件从而创建 NamingEvent 的监听器
- 第三阶段:服务提供方两次注册服务到 Nacos 上,但由于第二阶段的 NamingEvent 监听器未正常创建导致无法收到服务变更通知后重新触发 DubboMetadataService 代理类 的创建
- 第四阶段:创建 NacosWatch ,并启动定时任务每隔30s发布一次 HeartbeatEvent 事件。
- 第五阶段:监听到 ContextRefreshedEvent事件触发执行DubboCloudRegistry.preInit ,但因为第一阶段已经触发过一次 DubboCloudRegistry.preInit 了,所以本次不会执行DubboMetadataService 代理类 的创建逻辑。
- 假设没有第一阶段,如果这个阶段执行不是在第三阶段执行完成之后,也无法正常执行 DubboMetadataService 代理类 的创建
- 第六阶段:NacosWatch start 后第一次执行 run 方法,发布 HeartbeatEvent 事件并成功创建 NamingEvent 的监听器,
- 此时由于第三阶段已经执行完成,服务提供方没有特殊情况不会再发布服务变更的相关消息,因此无法监听到 NamingEvent 事件从而重新触发 DubboMetadataService 代理类 的创建。
- 30s后第一次触发,这如果服务提供方注册nacos不是在30s后注册,那就无法正常执行 DubboMetadataService 代理类 的创建
从上述六个阶段可以看出DubboMetadataService 代理类 无法被创建的核心原因有如下两点:
- DubboCloudRegistry.preInit 执行在服务提供方注册之前
- NamingEvent 监听器的注册在服务提供方注册之前之后。
而时序图中有个被忽视的阶段,由于执行周期较早,且该阶段执行是在第三阶段服务提供方 Nacos 注册之前,本文的解决方案核心是通过监听服务提供方 Nacos 注册实现的,所以不会出现服务提供方将本地URL注册到 DubboServiceMetadataRepository.allExportUrls 后 ,服务消费方才获取URL
解决方案分析
要解决该问题有几种思路方案:
1.DubboCloudRegistry.preInit 执行在服务提供方注册之后
存在问题: 跨服务不好从代码层面控制执行顺序。
2.让NamingEvent 监听器注册在服务提供方之前注册
存在问题: 和第(1)点一样的问题,跨服务顺序不好控制执行顺序。
3.确保NamingEvent 监听器注册好后再执行DubboCloudRegistry.preInit
当NamingEvent 监听器注册好后分两种情况①.服务提供方Nacos注册在DubboCloudRegistry.preInit 之前,那么DubboCloudRegistry.preInit 就可以正常创建DubboMetadataService 代理类
②.服务提供方Nacos注册在DubboCloudRegistry.preInit 之后,那么DubboCloudRegistry.preInit 虽然创建 DubboCloudRegistry.preInit 失败了,但是当服务提供方Nacos注册后会触发NamingEvent 监听器从而创建DubboCloudRegistry.preInit
存在问题:一开始我是想使用这种方法,但是要使用这种方法得保证Bean创建按如下流程图的顺序执行。但bean的创建顺序不好控制,所以放弃了。
- 主动进行NamingEvent 监听器注册后,注册后为防止服务提供方住的nacos发生在监听器注册前,所以主动触发一次DubboMetadataService 代理类 的创建,这样即使主动触发创建失败,后续注册也能走NamingEvent 监听器进行创建。
这也是本文使用的方法,关键点如下:
1).什么时候创建 NamingEvent :为了尽量复用框架本身的代码,创建需要在 DubboServiceMetadataRepository 和DubboServiceDiscoveryAutoConfiguration.NacosConfigurationServiceDiscoveryAutoConfiguration.NacosConfiguration 的 Bean 创建之后,所以选择实现 SmartInitializingSingleton 接口,该接口可以在 spring 创建完所有 bean 后执行。
2).如何主动触发 DubboMetadataService代理类 的创建:创建的方式有两种
①.通过 DubboCloudRegistry.preInit 触发创建,但是该方法是私有的,调不到。而且该方法只执行一次,所以可以放弃了
②.通过服务变更事件触发创建,如下图所示,在 DubboCloudRegistry.onApplicationEvent 中订阅了 ServiceInstanceChangedEvent 事件,所以可以主动发布该事件触发服务变更事件从而触发 DubboMetadataService代理类 的创建
具体实现如下
@Component public class DubboSubscribedServicesChangedEvent implements SmartInitializingSingleton { @Autowired private DubboServiceMetadataRepository dubboServiceMetadataRepository; @Autowired private DiscoveryClient discoveryClient; @Autowired private ApplicationEventPublisher applicationEventPublisher; @Override public void afterSingletonsInstantiated() { //最终目的是用于创建NamingEvent的监听器 dubboServiceMetadataRepository.initSubscribedServices(); //获取dubbo订购的服务 Set<String> subscribedServices = dubboServiceMetadataRepository.getSubscribedServices(); if (subscribedServices.contains("*")) { subscribedServices = new HashSet<>(discoveryClient.getServices()); } for (String serviceName : subscribedServices) { List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceName); //必须得判空,否则这边通知服务变更成0实例,nacos那边又刚好注册一个实例,两边并行执行会出现问题 if(CollectionUtils.isNotEmpty(serviceInstances)){ //主动发送服务变更事件,触发创建DubboMetadataService的代理类 ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent(serviceName, serviceInstances); applicationEventPublisher.publishEvent(event); } } } }