Eureka 服务实例上下线

Introduction

默认的Spring Eureka服务器,服务提供者和服务调用者配置不够灵敏,总是服务提供者在停掉很久之后,服务调用者很长时间并没有感知到变化。或者是服务已经注册上去了,但是服务调用方很长时间还是调用不到(发现不了这个服务)。

在实际情况中,Eureka 的默认设置是不符合我们生产要求的,一般情况下,我们的生产要求:

  1. ServiceA 下线一台实例后,API-Gateway 的调用不能失败;
  2. ServiceB 下线一台实例后,ServiceA 的 Feign 调用不能失败;
  3. 服务上线下线,Eureka服务能够快速感知;

涉及原理知识

Eureka的两层缓存问题

EurekaServer 默认有两个缓存,一个是 ReadWriteMap,另一个是 ReadOnlyMap。有服务提供者注册服务或者维持心跳时时,会修改 ReadWriteMap。当有服务调用者查询服务实例列表时,默认会从 ReadOnlyMap 读取(这个在原生Eureka可以配置,SpringCloud Eureka中不能配置,一定会启用ReadOnlyMap读取),这样可以减少 ReadWriteMap 读写锁的争用,增大吞吐量。EurekaServer 定时把数据从 ReadWriteMap 更新到 ReadOnlyMap 中。

心跳时间

客户端注册服务后,会定时心跳。这个根据客户端的 Eureka 配置中的服务刷新时间决定。还有个配置是客户端的服务过期时间,但是 EurekaServer 默认情况下是忽略客户端的这个字段。需要配置好 EurekaServer 的扫描失效时间,才会触发 EurekaServer 的主动失效机制。

在这个机制启用下:每个客户端会发送自己的服务过期时间上去,EurekaServer 会定时检查每个客户端的服务过期时间和上次心跳时间,如果在服务过期时间内没有收到过任何一次心跳,同时没有处于保护模式下,则会将这个实例从ReadWriteMap中去掉。

调用者服务从Eureka拉列表的轮训间隔

1
eureka.client.registry-fetch-interval-seconds

表示 Eureka Server 间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒。

1
eureka.instance.lease-expiration-duration-in-seconds

表示 Eureka Server 至上一次收到 Client 的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该 Instance。

默认为90秒,如果该值太大,则很可能将流量转发过去的时候,该instance已经不存活了;如果该值设置太小了,则instance则很可能因为临时的网络抖动而被摘除掉。因此,该值至少应该大于lease-renewal-interval-in-seconds。

eureka.instance.lease-renewal-interval-in-seconds:表示eureka client发送心跳给server端的频率。
lease-expiration-duration-in-seconds后,server端没有收到client的心跳,则将摘除该instance。除此之外,如果该instance实现了HealthCheckCallback,并决定让自己unavailable的话,则该instance也不会接收到流量。

Zuul - Ribbon缓存

在 Zuul 内部进行 Routing 和 Load Balance 时候,为了保证 HA,不受 Eureka 掉线的影响,内存中会有一个 Server List 缓存。


Practice

Client

  1. 服务过期时间,默认90s
1
eureka.instance.lease-expiration-duration-in-seconds=9

超过这个时间没有接收到心跳 EurekaServer 就会将这个实例剔除。EurekaServer 一定要设置 eureka.server.eviction-interval-timer-in-ms 否则这个配置无效,这个配置一般为服务刷新时间配置的三倍。

  1. 服务刷新时间配置,每隔这个时间会主动心跳一次,默认30s
1
eureka.instance.lease-renewal-interval-in-seconds=3
  1. 拉服务列表时间间隔,默认30s
1
eureka.client.registryFetchIntervalSeconds=5
  1. ribbon刷新时间,默认30s
1
ribbon.ServerListRefreshInterval=5000

Server

  1. 禁用Eureka的ReadOnlyMap缓存
1
eureka.server.use-read-only-response-cache: false
  1. 启用主动失效,并且每次主动失效检测间隔为3s
1
2
3
4
5
eureka.server.eviction-interval-timer-in-ms: 3000

# eureka.server.responseCacheUpdateInvervalMs
# eureka.server.responseCacheAutoExpirationInSeconds
# 在启用了主动失效后其实没什么用了。默认值180s

Zuul - Retry

1
2
3
4
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
#(是否所有操作都重试,若false则仅get请求重试)
ribbon.OkToRetryOnAllOperations:true
#(重试负载均衡其他实例最大重试次数,不含首次实例)
ribbon.MaxAutoRetriesNextServer:3
#(同一实例最大重试次数,不含首次调用)
ribbon.MaxAutoRetries:1
ribbon.ReadTimeout:30000
ribbon.ConnectTimeout:3000
#(那些状态进行重试)
ribbon.retryableStatusCodes:404,500,503
# (重试开关)
spring.cloud.loadbalancer.retry.enable:true

Spring Cloud Gateway

https://juejin.im/post/5ba0ea35f265da0afe62d7a4

Client 主动通知 Eureka 下线

如果是 Spring Boot 应用,可以调用以下代码通知 Eureka,本服务实例暂停提供服务。

1
DiscoveryManager.getInstance().shutdownComponent();

另外还可以通过 HTTP API 接口删除实例注册信息来实现服务下线。


Reference