Spring cloud系列十六 Eureka各个组件详解和相关配置详细说明

1,821 阅读13分钟

Eureka概述

经过一年的学习,对Spring Cloud了解加深,所以这里重新书写一下对Eureka的理解和大家分享

Spring Cloud针对服务注册与发现,进行了一层抽象,并提供了三种实现: Eureka(支持得最好)、Consul、Zookeeper。本文详细讲解Eureka的用法。

Eureka角色

这里写图片描述

Eureka 是 Netflix 开源的服务注册发现组件,服务端通过 REST 协议暴露服务,提供应用服务的注册和发现的功能。 所有的Eureka服务都被称为实例(instance)。Eureka服务又分为两类角色:Eureka Server和Eureka Client Eureka-Client又分为Application Provider 和Application Consumer Application Provider :服务提供者,内嵌 Eureka-Client ,它向 Eureka-Server 注册自身服务、续约、下线等操作 Application Consumer :服务消费者,内嵌 Eureka-Client ,它从 Eureka-Server 获取服务列表,分为全量获取和增量。

注意:Application Provider 和 Application Consumer 只是角色,同一个服务即可以是服务的提供者,又可以是服务的消费者。甚至,在注册中心的集群环境下,Eureka Server,即可以Eureka Server,又是Eureka Client。 Eureka Server比较好理解,Eureka Client是他向其它Eureak Server同步消息,它又变成了Eureker Client

Eureka的配置主要分为三类:

  • eureka.instance.*: Eureka实例注册配置项,这些是无论Eureka是做Server,Client都需要的公共配置项。对应的配置类为org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean。
  • eureka.server.*: Eureka做为Eureka Server才需要配置的选项,即使用eureka做注册中心时才需要配置这个选项。对应的配置类为org.springframework.cloud.netflix.eureka.EurekaClientConfigBean。
  • eureka.client.*: Eureka做为Eureka Client才需要配置的选项,即服务需要向注册中心注册或使用注册中心中的服务时才需要配置这些选项。对应的配置类为org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean

Eureka架构图

这里写图片描述

首次注册

  1. 服务提供者在启动会向Eureka Server注册信息(包括服务名(Eureka Service Id)、service id、ip、port、心跳周期等),EurekaServer获取注册请求,将服务实例信息存入到读写缓存中.
  2. 服务消费者根据Eureka service id向Eureka Server获取要访问服务的注册信息,第一次请求会一次性拉取对应Eureka service id的全部服务实例信息
  3. Eureka Server收到请求后,会首先在只读缓存查找,如果找到则直接返回,否则再查找读写缓存,如果找到则将再存入到只读缓存中,然后返回查找结果
  4. 服务提供者获取信息的,将服务实例信息缓存到本地,在使用时根据算法从N个服务中中选取一个服务并向这个服务发送请求

相关的配置

# 本服务注册到注册到服务器的名称, 这个名称就是后面调用服务时的服务标识符,即Eureka Service Id
spring.application.name: icc-transfer

# EurekaClientConfig,
# 配置 Eureka-Server 地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:10761/eureka/  
# 该实例是否向 Eureka  Server注册自己
eureka.client.register-with-eureka: true
# 该实例是否向 Eureka 服务器获取所有的注册信息表
eureka.client.fetch-registry: true

续约/心跳/下线:

  1. 服务提供者会定时向Eureka Server发送心跳,默认30s
  2. Eureka Server收到心跳后,会更新对应的服务实例信息,如果服务的状态有变化则将实例的变化加入到"最近租约变更记录队列"中
  3. Eureka Server有个实例过期清理定时器,如果在指定时间内没有收到心跳(默认90s),则认为服务已经下线,会从读写缓存中移除此实例,将并此变化更新“最近租约变更记录队列”。通常建议将存活的最长时间设置为3个心跳
  4. 服务消费者也有个定时任务会定时去更新服务实例信息(默认30s),第一次全量拉取服务实例,会读取所有的实例信息; 之后使用增量拉取服务实例信息,Eureka Server根据"最近租约变更记录队列",告诉请求方哪些服务实例有变化,只返回变化的实例信息。客户端根据返回的增量信息更新本地缓存。我们也可以禁用增量获取服务实例实例,每次使用全量获取服务实例信息

服务实例下线,又分为两种:

  • 主动下线,在服务实例结束前,主动通知Eureka Server,在默认配置下,此时服务消费者最长在30s左右知道此服务已经下线
  • 被动下线,服务进度崩溃/网络异常,此时服务消费者最长在(3次心跳+一次刷新频率30s)(共约120s左右)内知道此服务已经下线
# EurekaClientConfig,重在 Eureka-Client,例如, 连接的 Eureka-Server 的地址、获取服务提供者列表的频率、注册自身为服务提供者的频率等等 
## 服务发现相关的配置
# 是否从 Eureka-Server 拉取服务实例注册信息,默认值为true
eureka.client.fetch-registry: true
# 从 Eureka-Server 拉取注册服务实例信息频率,默认:30 秒
eureka.client.registry-fetch-interval-seconds: 30 

# 是否禁用增量获取服务实例注册信息
eureka.disableDelta: false
# EurekaInstanceConfig,重在服务实例,例如,等等。此处应用指的是,Application Consumer 和 Application Provider。
# 服务实例配置
# 心跳,租约续约频率,单位:秒
eureka.instance.lease-renewal-interval-in-seconds: 30
#  eureka server多久没有收到心跳,则表示对应的实例过期,单位:秒。
eureka.instance.lease-expiration-duration-in-seconds: : 90s
# Eureka Server端服务实例租约过期定时任务执行频率
eureka.server.eviction-interval-timer-in-ms: 60s 

缓存

在上图中Eureka Server有两个缓存,一个只读缓存,一个是读写缓存。

  1. 读写缓存:每次有服务实例状态变化(如注册服务、下线等)只会更新读写缓存
  2. 只读缓存永不过期,但是可能存在过期数据。此时为了保证只读数据的准确性,会有个定时器定时同步两个缓存,然后将状态变化的服务实例添加到"最近租约变更记录队列"。执行频率默认30s

为了提高实时性,可以关闭只读缓存。

"最近租约变更记录队列"里的数据也有有效期,默认为180s。

  1. 当有注册、状态变更、下线时,会将创建最近租约变更记录加入此队列中
  2. 用于注册信息增量获取
  3. 后台任务定时顺序扫描队列,当数据最后更新时间超过一定时长后进行移除

相关配置

# 是否开启只读请求响应缓存。响应缓存 ( ResponseCache ) 机制目前使用两层缓存策略。优先读取只读缓存,读取不到后读取固定过期的读写缓存。
eureka.server.use-read-only-response-cache: true
# 只读缓存更新频率,单位:毫秒。只读缓存定时更新任务只更新读取过请求 ,因此虽然永不过期,也会存在读取不到的情况。
eureka.server.response-cache-update-interval-ms: 30s
#getResponseCacheAutoExpirationInSeconds() :读写缓存写入后过期时间,单位:秒。
eureka.server.response-cache-auto-expiration-in-seconds: 180s

# 移除队列里过期的租约变更记录的定时任务执行频率,单位:毫秒。默认值 :30 * 1000 毫秒。
eureka.server.delta-retention-timer-interval-in-ms: 30s
# 租约变更记录过期时长,单位:毫秒。默认值 : 3 * 60 * 1000 毫秒。
eureka.server.retention-time-in-m-s-in-delta-queue: 180s

Eureka Server – 自我保护机制

概念:

  • Renews threshold: Eureka Server 期望每分钟最少收到服务实例心跳的总数

    • Renews threshold = 当前注册的服务实例数 x 2 * 自我保护系数( eureka.renewalPercentThreshold )
    • 之所以 x 2,是因为Eureka 硬编码认为每个实例每 30 秒 1 个心跳,即1分钟2个心跳
  • Renews (last min): Eureka Server 最近 1 分钟收到服务实例心跳的总数

    当启动自我保护时,如果Eureka Server最近1分钟收到服务实心跳的次数小于阈值(即预期的最小值),则会触发自我保护模式,此时Eureka Server此时会认为这是网络问题,它不会注销任何过期的实例。等到最近收到心跳的次数大于阈值后,则Eureka Server退出自我保护模式

    刚开始接触spring cloud时,碰到这样的情况:服务提供者明明已经挂掉了,但是服务消息方要过好久(如30s)才知道。更糟糕的是服务提供者进度奔溃,没来的及发送下线请求到注册中心,则服务消费者可能需要120s时间才知道。面对这样的情况,我们总是尝试将心跳频率、刷新频率、注册信息在注册中心的生存时间等配置改到最小,以达到服务提供方一挂掉,服务调用方书上马上知道。但是这个方案有缺陷,中间的时差总时存在,无法完全消除。而且频繁的心跳会增加网络和服务的不必要的性能消耗,如果网络有抖动,则有可能出现误报。 不推荐这么修改的一个重要原因是Eureka 是AP系统,不是CP系统,为了可用性,它不保证实时一致性,它只保证最终一致性。还有一个原因是Eureka中有一部分硬编码,默认认为每分钟每个服务实例的心跳是2次,此值用于计算Eureka每分钟理论上应该收到的心跳的个数,此值会用于计算Eureka开启自我保护

    在生产环境,推荐开启,避免网络抖动对服务的影响。有这样的极端场景,比如注册中心单独部署在一个集群中,所有服务向其注册服务完毕后,突然注册中心所在的集群网络和其它服务网络断开,但是服务实例之间是可达的,整个服务可以正常工作。此时如果没有开启自我保护机制,经过Ns后,注册中心清理所有的注册服务,然后网络恢复了,那么其它服务进行更新时,会认为其它服务已经掉线,则从本地移除所有的服务请求,则整个服务变的不可用 关于自我保护阈值,需要设置合理值。比如我们将全部服务均匀地部署在2台服务器,则需要设置阈值小于50%,否则如果出来一台服务器坏掉了,则此时就要求注册中心移除一般的服务注册中心是合理的,不应该启动自我保护

相关配置

# 配置在Eureka Server端的配置文件 
#是否开启自我保护模式。
eureka.server.enable-self-preservation: false
#开启自我保护模式比例,超过该比例后开启自我保护模式。
eureka.server.renewal-percent-threshold: 0.85
#自我保护模式比例更新定时任务执行频率。
eureka.server.renewal-threshold-update-interval-ms: 900s

执行清理过期租约逻辑

计算最大允许清理租约的数量,后计算允许清理租约的数量 本次最大允许清理租约数量 = 当前注册的应用实例数 -(当前注册的应用实例数 * renewalPercentThreshold) 实际清理租约数量 = Math.min(所有过期注册信息个数,最大允许清理租约数量) 随机清理过期的租约 如果同时过期大量注册信息,则需要好久才能将注册信息清理完毕

renewalPercentThreshold = 0.85 默认配置,服务实例会分批逐步过期

这里写图片描述

是否开启自我保护的差别,在于是否执行清理过期租约逻辑。如果想关闭分批逐步过期,设置 renewalPercentThreshold = 0 。

Eureka集群

这里写图片描述

为了提高高可用,Eureka是提供集群功能。不仅Eureka Client端可以集群,做为注册中心的Eureka Server也可以集群 。

多个Eureka Client进程向注册中心注册的Eureka Service Id相同,则被认为是一个集群。 多个Eureka Server相互注册,组成一个集群

每个Eureka Server注册的消息发生变化时,会各个服务之间定时同步,中间过程每个服务的数据可能不一致,但是最终会变得一致 Eureka Server集群之间的状态是采用异步方式同步的,所以不保证节点间的状态一定是一致的,不过基本能保证最终状态是一致的。

Eureka Server 服务之间之间同步:Eureka Server配置中心集群

每个Eureka Server注册的消息发生变化时,会各个服务之间定时同步,中间过程每个服务的数据可能不一致,但是最终会变得一致 Eureka Server集群之间的状态是采用异步方式同步的,所以不保证节点间的状态一定是一致的,不过基本能保证最终状态是一致的。

集群重要的类com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl

为了保证集群里所有Eureka Server节点的状态同步,所有以下操作都会同步到集群的所有服务上:服务注册(Registers)、服务注册(Registers)、服务更新(Renewals)、服务取消(Cancels),服务超时(Expirations)和服务状态变更(Status Changes)。以下是一些部分重要

  • syncUp:在Eureka Server重启或新的Eureka Server节点加进来的,会执行初始化,从而能够正常提供服务。当Eureka server启动时,他会从其它节点获取所有的注册信息。如果获取同步失败,它在一定时间(此值由决定)内拒绝服务。
  • replicateToPeers: 复印所有的eureka操作到集群中其他节点,请求再次转发到其它的Eureka Server,调用同样的接口,传入同样的参数,除了会在header中标记isReplication=true,从而避免重复的replicate
  • register: 注册登录的实例,并且复印此实例的信息到所有的eureka server的节点。如果其它Eureka server调用此节点,只在本节点更新实例信息,避免通知其他节点执行更新
  • renew:心跳
  • cancel

新的Eureka Server节点加入集群后的影响

当有新的节点加入到集群中,会对现在Eureka Server和Eureka Client有什么影响以及他们如何发现新增的Eureka Server节点:

  • 新增的Eureka Server:在Eureka Server重启或新的Eureka Server节点加进来的,它会从集群里其它节点获取所有的实例注册信息。如果获取同步失败,它会在一定时间(此值由决定决定)内拒绝服务。
  • 已有Eureka Server如何发现新的Eureka Server:
    • 已有的Eureka Server:在运行过程中,Eureka Server之间会定时同步实例的注册信息。这样即使新的Application Service只向集群中一台注册服务,则经过一段时间会集群中所有的Eureka Server都会有这个实例的信息。那么Eureka Server节点之间如何相互发现,各个节点之间定时(时间由eureka.server.peer-eureka-nodes-update-interval-ms决定)更新节点信息,进行相互发现。
    • Service Consumer:Service Consumer刚启动时,它会从配置文件读取Eureka Server的地址信息。当集群中新增一个Eureka Server中时,那么Service Provider如何发现这个Eureka Server?Service Consumer会定时(此值由eureka.client.eureka-service-url-poll-interval-seconds决定)调用Eureka Server集群接口,获取所有的Eureka Server信息的并更新本地配置。

相关配置

== Eureka-Server 集群同步相关
# Eureka-Server 启动时,从远程 Eureka-Server 读取不到注册信息时,多长时间不允许 Eureka-Client 访问。
eureka.server.wait-time-in-ms-when-sync-empty:5分钟
#Eureka-Server 集群节点更新频率,单位:毫秒。
eureka.server.peer-eureka-nodes-update-interval-ms:
# 初始化实例信息到Eureka服务端的间隔时间,单位为秒
eureka.client.initial-instance-info-replication-interval-seconds: 40
# 更新实例信息的变化到Eureka服务端的间隔时间,单位为秒
eureka.client.instance-info-replication-interval-seconds: 30