eureka在关闭自我保护情况下过期服务不及时下线问题跟踪
1、微服务中使用eureka注册中心的三种角色eureka server注册中心
注册中心作为存储服务,提供各种服务提供方注册的服务信息,eureka正常启动后可使用 http://localhost:8761/ 查看已注册的服务信息及相关配置信息,http://localhost:8761/eureka/apps/feign-service 可查看某个服务的明细信息,其中feign-service 即服务的名称。eureka: server: enable-self-preservation: false eviction-interval-timer-in-ms: 10000
参数 enable-self-preservation 为是否开启eureka自我保护参数,eviction-interval-timer-in-ms 为清除过期服务时间间隔,如设置成每10秒清除一次过期服务,启动eureka注册中心服务后每间隔10秒会有Running the evict task with compensationTime 3ms 日志;服务提供方
将服务信息注册到eureka注册中心,并可配置维持心跳检测(续期)时间((默认30秒)及过期时间(默认90秒)。 eureka: client: register-with-eureka: true fetch-registry: true instance: lease-renewal-interval-in-seconds: 1 lease-expiration-duration-in-seconds: 10
register-with-eureka 表示是否将服务注册到eureka中,服务提供方应设置为true ;fetch-registry 表示是否从注册中心获取配置,服务消费方应配置为true ;lease-renewal-interval-in-seconds 表示续期时间间隔,即1秒心跳检测一次,为了演示方便调短时间;lease-expiration-duration-in-seconds 续期超时时间,即超过此时间后服务检测不到心跳即过期。服务消费方
从注册中心获取需要调用的服务提供方相关信息 2、跟踪失效服务的清除
将注册中心的清除时间间隔 eviction-interval-timer-in-ms 改为1000 ,即每秒钟触发一次清除过期服务;
将服务提供方心跳检测时间 lease-renewal-interval-in-seconds 改为1 ,每秒续一次时间,并将过期时间lease-expiration-duration-in-seconds 设置为10 ,即应该在10 秒后没有收到心跳即过期,启动服务后可在注册中心查看相关的参数是否生效http://localhost:8761/eureka/apps/feign-service ,在instance->leaseInfo 中可看到以下配置;1 10
按照以上的设置,如果服务提供方在 6:00:00 的时间挂掉的话,按eureka每秒清除一次过期服务,且服务提供方的续期过期时间10秒 ,服务应该在6:00:11 左右的时间清除掉,而实际上服务却是在6:00:21 左右的时间点才清除;经过反复的测试验证没有按这个预期内的时间清除掉这个服务;
最后在源码中找到答案
查看类 com.netflix.eureka.registry.AbstractInstanceRegistry 中的public void evict(long additionalLeaseMs) 方法,因为演示中只注册了一个服务,所以省略掉其他非关键部分如下// We collect first all expired items, to evict them in random order. For large eviction sets, // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it, // the impact should be evenly distributed across all applications. List> expiredLeases = new ArrayList<>(); for (Entry>> groupEntry : registry.entrySet()) { Map> leaseMap = groupEntry.getValue(); if (leaseMap != null) { for (Entry> leaseEntry : leaseMap.entrySet()) { Lease lease = leaseEntry.getValue(); if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) { expiredLeases.add(lease); } } } }
大概意思是收集到全部过期的服务,并随机清除,其中最关键的部分 isExpired 判断如下/** * Checks if the lease of a given {@link com.netflix.appinfo.InstanceInfo} has expired or not. * * Note that due to renew() doing the "wrong" thing and setting lastUpdateTimestamp to +duration more than * what it should be, the expiry will actually be 2 * duration. This is a minor bug and should only affect * instances that ungracefully shutdown. Due to possible wide ranging impact to existing usage, this will * not be fixed. * * @param additionalLeaseMs any additional lease time to add to the lease evaluation in ms. */ public boolean isExpired(long additionalLeaseMs) { return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs)); }
这里解析了 renew() 这个方法做了错误的事情,使用lastUpdateTimestamp 加了duration ,所以实际的过期是2 倍的duration 时间,接下来看看类com.netflix.eureka.lease.Lease 中的renew() 方法;/** * Renew the lease, use renewal duration if it was specified by the * associated {@link T} during registration, otherwise default duration is * {@link #DEFAULT_DURATION_IN_SECS}. */ public void renew() { lastUpdateTimestamp = System.currentTimeMillis() + duration; }
果然是在最后更新时间中增加了 duration ,在演示中去掉这个duration 即符合预期了
通常情况下如果我们需要平滑升级服务,需要先将原来的微服务逐个标识成OUT_OF_SERVICE,curl -X PUT http://root:password@localhost:8761/eureka/apps/feign-service/localhost:feign-service:8888/status?value=OUT_OF_SERVICE,在下一个续约周期中将会更新服务提供方的实例信息,再将此服务下线,从而避免因服务的升级造成服务不可用。