问题描述 eureka client开启健康检查,actuator开启probes并且显示所有详情,确保不是其它indicator导致的。
spring cloud版本Hoxton.SR10,spring boot版本2.3.9.RELEASE,k8s的版本不重要。
服务启动后,eureka server显示服务状态为OUT_OF_SERVICE,调用服务actuator health端点,除了eureka server indicator显示的是OUT_OF_SERVICE,其它全部是up。
问题查找 搜遍整个google,没有解决办法,但是github上有个issue,看了下comments,基本上也帮不上忙,开始debug。
确定方向 spring cloud netflix eureka会有EurekaHealthCheckHandler将服务的健康状态同步到eureka的InstanceStatus,所以确定是由健康检查的indicators引起的。
源码查看 通过EurekaHealthCheckHandler的getStatus的方法,找到调用方为DiscoveryClient的refreshInstanceInfo方法,
然后再找到refreshInstanceInfo的调用方是InstanceInfoReplicator的run方法,然后再找到InstanceInfoReplicator线程初始化的代码在DiscoveryClient的initScheduledTasks。
那就清楚了,discovery client初始化的时候会启动一个定时任务,定时去获取InstanceInfo。我们继续debug。
EurekaHealthCheckHandler的getStatus在第一次调用时会返回OUT_OF_SERVICE,然后接下来只要服务没有问题,会一直返回UP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 protected Status getStatus (StatusAggregator statusAggregator) { Status status; Set<Status> statusSet = new HashSet <>(); if (healthIndicators != null ) { statusSet.addAll( healthIndicators.values().stream().map(HealthIndicator::health) .map(Health::getStatus).collect(Collectors.toSet())); } if (reactiveHealthIndicators != null ) { statusSet.addAll(reactiveHealthIndicators.values().stream() .map(ReactiveHealthIndicator::health).map(Mono::block) .filter(Objects::nonNull).map(Health::getStatus) .collect(Collectors.toSet())); } status = statusAggregator.getAggregateStatus(statusSet); return status; }
debug发现是ReadinessStateHealthIndicator返回OUT_OF_SERVICE。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ReadinessStateHealthIndicator extends AvailabilityStateHealthIndicator { public ReadinessStateHealthIndicator (ApplicationAvailability availability) { super (availability, ReadinessState.class, (statusMappings) -> { statusMappings.add(ReadinessState.ACCEPTING_TRAFFIC, Status.UP); statusMappings.add(ReadinessState.REFUSING_TRAFFIC, Status.OUT_OF_SERVICE); }); } @Override protected AvailabilityState getState (ApplicationAvailability applicationAvailability) { return applicationAvailability.getReadinessState(); } }
继续往里看,找到applicationAvailability的实现类ApplicationAvailabilityBean,找到调用方法getState。
1 2 3 4 5 @Override public <S extends AvailabilityState > S getState (Class<S> stateType) { AvailabilityChangeEvent<S> event = getLastChangeEvent(stateType); return (event != null ) ? event.getState() : null ; }
代码逻辑很简单,获取事件后,再获取内部的state。ApplicationAvailabilityBean实现了ApplicationListener,那么现在就明白了,在调用getState之前还未接收到事件。
1 2 3 4 5 6 @Override @SuppressWarnings("unchecked") public <S extends AvailabilityState > AvailabilityChangeEvent<S> getLastChangeEvent (Class<S> stateType) { return (AvailabilityChangeEvent<S>) this .events.get(stateType); }
问题原因 在服务未完全启动的时候就调用了indicator的getState的地方有三处,第一处,没有问题。
1 2 3 4 5 6 7 8 public void start (int initialDelayMs) { if (started.compareAndSet(false , true )) { instanceInfo.setIsDirty(); Future next = scheduler.schedule(this , initialDelayMs, TimeUnit.SECONDS); scheduledPeriodicRef.set(next); } }
第二处,没有问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 statusChangeListener = new ApplicationInfoManager .StatusChangeListener() { @Override public String getId () { return "statusChangeListener" ; } @Override public void notify (StatusChangeEvent statusChangeEvent) { logger.info("Saw local status change event {}" , statusChangeEvent); instanceInfoReplicator.onDemandUpdate(); } };
第三处,那肯定是这了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public void registerHealthCheck (HealthCheckHandler healthCheckHandler) { if (instanceInfo == null ) { logger.error("Cannot register a healthcheck handler when instance info is null!" ); } if (healthCheckHandler != null ) { this .healthCheckHandlerRef.set(healthCheckHandler); if (instanceInfoReplicator != null ) { instanceInfoReplicator.onDemandUpdate(); } } }
修复方法
actuator关闭probes;
eureka client关闭健康检查;
修改ReadinessStateHealthIndicator ReadinessState.REFUSING_TRAFFIC map为 Status.DOWN(DOWN和OUT_OF_SERVICE区别还是很大的,和ReadinessStateHealthIndicator的设计思想有出入。);
如果events为空,ApplicationAvailabilityBean的getState返回ReadinessState.ACCEPTING_TRAFFIC。
修复方法分析
我们的服务是部署在k8s上的并且开启了探针,升级到这个版本有一部分是为了这个探针,no;
关闭后可能因为一些原因,会导致服务不可用,但是服务状态还是UP,no;
无法修改源码,no;
events为空的时候,ReadinessState怎么能是ACCEPTING_TRAFFIC?那怎么办?先让代码跑起来吧!等待官方修复。
临时修复 主要思路就是,继承ApplicationAvailabilityBean,重写getState,代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class IvcApplicationAvailabilityBean extends ApplicationAvailabilityBean { @Override public <S extends AvailabilityState > S getState (Class<S> stateType) { AvailabilityChangeEvent<S> event = getLastChangeEvent(stateType); return (event != null ) ? event.getState() : getDefaultState(stateType); } @SuppressWarnings("unchecked") private <S extends AvailabilityState > S getDefaultState (Class<S> stateType) { if (stateType == LivenessState.class) { return (S)LivenessState.BROKEN; } else if (stateType == ReadinessState.class) { return (S)ReadinessState.ACCEPTING_TRAFFIC; } return null ; } }
替换官方的ApplicationAvailabilityBean,代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Component public class IvcBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry (BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { String beanName = "applicationAvailability" ; BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(IvcApplicationAvailabilityBean.class); beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition()); } @Override public void postProcessBeanFactory (ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { } }
好了,这下子如果events为空,ReadinessState会返回ACCEPTING_TRAFFIC,mapping后就不会出现OUT_OF_SERVICE了.
可能的官方修法 Eureka Server可以手动设置服务状态为OUT_OF_SERVICE,然后要UP的话需要手动delete。
那么健康检查的OUT_OF_SERVICE,是不是也可以手动delete(至少现在不行)。