背景:
当我们更新微服务时,停止服务,然后再启动服务,这个时间内,服务是不可用的,但是nacos并不能立马感知到我们的微服务已经下线了,nacos是定时轮询当前的服务是否健康,当检测到服务不健康时,会移除当前服务,这个时间可以再nacos中进行设置检查时间,但是无论怎么设置,都会存在这种服务不可用的状态,尤其再访问高时。
解决方案:
服务滚动发布,当微服务下线时,使用钩子ShutdownHook函数来通知nacos下线。
具体实现:
首先当我们请求我们的网关时,网关会选择一个服务实例进行转发,那他这个实例是从哪里来的呢,这个关乎到我们的版本,我们使用的是springcloud alibaba 、openfeign,spingcloud已经自己研制了负载均衡组件,loadbalancer,这个loadbalancer目前有两种默认的负载均衡算法分别是
RandomLoadBalancer、
RoundRobinLoadBalancer
他们都实现了
ReactorServiceInstanceLoadBalancer接口,包括nacos也实现了这个接口
NacosLoadBalancer。关于这几个负载均衡器可自行了解
我们今天聊一下他的原理
首先,是从网关转发,网关根据自己的设置去找微服务实例,这个设置会判定是走本地缓存还是走实时读取nacos上的实例,如果是缓存,那么就存在服务下线延迟的问题,因为他需要根据时间去调度来更新缓存。所以我们的解决方案是,当我们服务下线时,将本地缓存清理,然后让服务去nacos上拉取最新的实例列表,这样就可以实现服务下线,也不会导致服务不可用的问题。
@Slf4j @Component public class ServiceChangeNotifier extends Subscriber<InstancesChangeEvent> { @Autowired private ApplicationContext context; @PostConstruct public void init() { // 注册当前自定义的订阅者以获取通知 NotifyCenter.registerSubscriber(this); } @Override public void onEvent(InstancesChangeEvent event) { String serviceName = event.getServiceName(); if (serviceName.contains(":")) { return; } String split = Constants.SERVICE_INFO_SPLITER; if (serviceName.contains(split)) { serviceName = serviceName.substring(serviceName.indexOf(split) + split.length()); } log.info("服务上下线: {}", serviceName); ObjectProvider<LoadBalancerCacheManager> cacheManagerProvider = context.getBeanProvider(LoadBalancerCacheManager.class); Cache cache = cacheManagerProvider.getIfAvailable().getCache(CachingServiceInstanceListSupplier.SERVICE_INSTANCE_CACHE_NAME); if (cache != null) { boolean present = cache.evictIfPresent(serviceName); log.info("本地缓存处理结果:{}",present); } } @Override public Class<? extends Event> subscribeType() { return InstancesChangeEvent.class; } }
NotifyCenter这个类,在初始化时,会将服务注册到nacos服务端,并且会触发关闭钩子(关键),具体看代码
这样,我们监听到nacos发布通知,然后直接执行本地缓存清理,即可完成缓存内的服务实例进行实时更新,完结。