Eureka解读

Eureka 简述

简单来说 ,Eureka 就是 Netflix 开源的一款提供服务注册和服务发现的产品,并且提供了 Java 客户端。当然在 SpringCloud 大力优化后的 Eureka,已经不仅仅只是用于 AWS 云,而是可以应用在任何需要使用注册中心的场景

Eureka 由两个组件组成:Eureka 服务端和 Eureka 客户端。Eureka 服务端就是注册中心。Eureka 客户端是一个 Java 客户端,用来简化与服务端的交互、作为轮询负载均衡器,并提供服务的故障切换支持

下面是 Eureka 的使用场景

在这里插入图片描述
从上面看 Eureka Server 担任注册中心的角色,提供了服务的发现和服务注册功能

  • Service Provider 服务提供者:将自身的服务注册到 Eureka Server,同时通过心跳检查服务的运行状态
  • Service Consumer 服务调用者:从 Eureka Server 得到注册的服务列表,找到对应的服务地址再调用并使用

Eureka Server 服务端的核心功能点

在这里插入图片描述

Eureka 客户端的注册

Eureka 的客户端服务启动的时候,它会发起一个 HttpPOST 请求到 Eureka 的服务端(注册中心),用于注册自己的信息

Eureka 的服务端(注册中心)保存 Eureka 客户端注册进来的信息实际上是一个 Map

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
  • 第一个 String 代表:客户端的服务名称,key = spring.application.name
  • 第二个 String 代表:该客户端实例的 ID(key = instanceId,因为一个服务有时不止一个实例)
  • Lease<InstanceInfo> 代表:该服务的 IP + 该服务的 HOST + 该服务的状态

Eureka 客户端的续约

Eureka 的客户端启动并成功注册到 Eureka 的服务端(注册中心)后, Eureka 的客户端会每隔 30 秒发送一次心跳(实际上就是 Http 的请求)来续约,通过续约来告知 Eureka 的服务端该 Eureka 的客户端还仍然存在。正常情况下,如果 Eureka 的服务端在 90 秒仍然没有收到 Eureka 的客户端的心跳,它会将实例从其注册表中删除,建议不要更改续约时间间隔

# 服务续约任务的调用间隔时间,默认为 30 秒
eureka.instance.lease-renewal-interval-in-seconds=30

# 服务失效的时间,默认为 90 秒
eureka.instance.lease-expiration-duration-in-seconds=90

Eureka 客户端的下线与剔除

Eureka 的客户端服务实例关闭时,服务实例会向 Eureka 服务器发送服务下线请求。发送请求后,该服务实例信息将从 Eureka 服务器的实例注册列表中删除

DiscoveryManager.getInstance().shutdownComponent();

Eureka 客户端的拉取注册表信息

Eureka Client 每隔 30s 会从 Eureka 服务器端拉取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。每次返回注册列表信息可能与 Eureka Client 的缓存信息不同,Eureka Client 自动处理。因此当所有的 Eureka Server 节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者,但是当服务有更改的时候会出现信息不一致

如果由于某种原因导致注册列表信息不能及时匹配,Eureka Client 则会重新获取整个注册表信息。Eureka Server 缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka ClientEureka Server 可以使用 JSON/XML 格式进行通讯。在默认情况下 Eureka Client 使用压缩 JSON 格式来获取注册列表的信息

# 启用服务消费者从注册中心拉取服务列表的功能
eureka.client.fetch-registry=true

# 设置服务消费者从注册中心拉取服务列表的间隔
eureka.client.registry-fetch-interval-seconds=30

Eureka 客户端的远程调用

Eureka Client 从注册中心获取到服务提供者信息后,就可以通过 Http 请求调用对应的服务;服务提供者有多个时,Eureka Client 客户端会通过 Ribbon 自动进行负载均衡

Eureka 服务端信息同步

Eureka Client 通过注册、心跳机制成功注册和续约后,Eureka Server 节点之间会同步当前客户端的状态信息

Eureka 服务端自我保护机制

默认情况下,如果 Eureka Server90s 内没有接收到某个微服务实例的心跳,它会将实例从其注册表中删除。但是,在微服务架构下服务之间通常都是跨进程调用,网络通信往往面临各种问题,比如微服务状态正常,网络分区故障,导致此实例被注销。为了解决这个问题,Eureka 引入了自我保护机制,也就是如果在 15 分钟内超过 85% 的节点都没有正常的心跳,那么 Eureka 就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

  • Eureka 不再从注册列表中移除因为长时间没有心跳而应该过期的服务
  • Eureka 仍然能够接受新服务的注册和查询请求,但是不会同步到其他节点上(既保证当前节点依然可用)
  • 当网络恢复正常时,当前实例新的注册信息会被同步到其他节点上

在这里插入图片描述
Eureka 自我保护机制是为了防止误杀服务而提供的一个机制。当个别客户端出现心跳失联时,则认为是客户端的问题,剔除掉客户端;当 Eureka 捕获到大量的心跳失败时,则认为可能是网络问题,进入自我保护机制;当客户端心跳恢复时,Eureka 会自动退出自我保护机制

如果在保护期内刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,即会调用失败。对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等

# 开启或者关闭保护机制,生产环境建议打开
eureka.server.enable-self-preservation=true

Eureka 的架构原理

再来看看 Eureka 集群的架构工作原理。我们假设有三台 Eureka Server 组成的集群,第一台 Eureka Server 在北京机房,另外两台 Eureka Server 在上海和西安机房。这样三台 Eureka Server 就组建成了一个跨区域的高可用集群,只要三个地方的任意一个机房不出现问题,都不会影响整个架构的稳定性

在这里插入图片描述

  • 从图中可以看出 Eureka Server 集群相互之间通过 Replicate 来同步数据,相互之间不区分主节点和从节点,所有的节点都是平等的。在这种架构中,节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的 serviceUrl 指向其他节点
  • 如果某台 Eureka Server 宕机,Eureka Client 的请求会自动切换到新的 Eureka Server 节点。当宕机的服务器重新恢复后,Eureka 会再次将其纳入到服务器集群管理之中。当节点开始接受客户端请求时,所有的操作都会进行节点间复制,将请求复制到其它 Eureka Server 当前所知的所有节点中
  • 另外 Eureka Server 的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。所以,如果存在多个节点,只需要将节点之间两两连接起来形成通路,那么其它注册中心都可以共享信息。每个 Eureka Server 同时也是 Eureka Client,多个 Eureka Server 之间通过 P2P 的方式完成服务注册表的同步
  • Eureka Server 集群之间的状态是采用异步方式同步的,所以不保证节点间的状态一定是一致的,不过基本能保证最终状态是一致的

Eureka 分区

Eureka 提供了 RegionZone 两个概念来进行分区,这两个概念均来自于亚马逊的 AWS

  • region:可以理解为地理上的不同区域,比如亚洲地区,中国区或者深圳等等。没有具体大小的限制。根据项目具体的情况,可以自行合理划分 region
  • zone:可以简单理解为 region 内的具体机房,比如说 region 划分为深圳,然后深圳有两个机房,就可以在此 region 之下划分出 zone1、zone2 两个 zone

上图中的 us-east-1c、us-east-1d、us-east-1e 就代表了不同的 ZoneZone 内的 Eureka Client 优先和 Zone 内的 Eureka Server 进行心跳同步,同样调用端优先在 Zone 内的 Eureka Server 获取服务列表,当 Zone 内的 Eureka Server 挂掉之后,才会从别的 Zone 中获取信息

Eureka 的工作流程

  1. Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息
  2. Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务
  3. Eureka Client 会每 30sEureka Server 发送一次心跳请求,证明客户端服务正常
  4. Eureka Server90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例
  5. 单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端
  6. Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式
  7. Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地
  8. 服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存
  9. Eureka Client 获取到目标服务器信息,发起服务调用
  10. Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除

Eureka Server 的数据存储与缓存机制

数据存储层

Eureka 的服务端(注册中心)保存 Eureka 客户端注册进来的信息实际上是一个 Map

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
  • 第一个 String 代表:客户端的服务名称,key = spring.application.name
  • 第二个 String 代表:该客户端实例的 ID(key = instanceId,因为一个服务有时不止一个实例)
  • Lease<InstanceInfo> 代表:该服务的 IP + 该服务的 HOST + 该服务的状态

缓存层

Eureka Server 为了提高响应效率,在缓存层面提供了两层的缓存结构

  • 一级缓存:readOnlyCacheMap,它本质上是一个 ConcurrentHashMap。依赖定时的从 readWriteCacheMap 同步数据,默认同步间隔时间是 30s。主要是为了供 Eureka Client 获取注册信息时使用。它的缓存更新依赖于定时器的更新,通过和 readWriteCacheMap 的值做对比,如果数据不一致,则以 readWriteCacheMap 的数据为准
  • 二级缓存:readWriteCacheMap,它本质上是一个 Guava 缓存
    readWriteCacheMap:它的数据主要同步于 ConcurrentHashMap 数据存储层。当获取缓存时判断缓存中是否没有数据,如果不存在此数据,则通过 CacheLoaderload 方法去加载,加载成功之后将数据放入缓存,同时返回数据。它的缓存过期时间是 180s,当服务下线,注册,状态改变等都会来清除缓存中的数据

Eureka Client 的缓存机制

  • Eureka Client 启动时会全量拉取注册表信息,启动完成后,会每隔 30sEureka Server 增量拉取信息,并保存在本地的缓存中
  • Eureka Client 增量拉取失败或者增量拉取之后对比 HashCode 发现不一致,就会执行全量拉取,同样会更新保存到本地的缓存
  • 对于服务的调用,如果涉及到 Ribbon 的负载均衡,而 Ribbon 对于这个服务实例列表也有自己的缓存,这个缓存每隔 30sEureka Client 的缓存更新

这么多的缓存机制可能就会造成一些问题,一个服务启动后可能最长需要 90s 才能被其它服务感知到:

  1. 首先,Eureka Server 会维护每 30s 更新的响应缓存
  2. Eureka Client 对已经获取到的注册信息也做了 30s 缓存
  3. 负载均衡组件 Ribbon 也有 30s 缓存

这三个缓存加起来,就有可能导致服务注册最长延迟 90s ,这个需要我们在特殊业务场景中注意其产生的影响

Eureka Server 的常用配置

#服务端开启自我保护模式,前面章节有介绍
eureka.server.enable-self-preservation=true

#扫描失效服务的间隔时间(单位毫秒,默认是60*1000)即60秒
eureka.server.eviction-interval-timer-in-ms= 60000

#间隔多长时间,清除过期的 delta 数据
eureka.server.delta-retention-timer-interval-in-ms=0

#请求频率限制器
eureka.server.rate-limiter-burst-size=10

#是否开启请求频率限制器
eureka.server.rate-limiter-enabled=false

#请求频率的平均值
eureka.server.rate-limiter-full-fetch-average-rate=100

#是否对标准的client进行频率请求限制。如果是false,则只对非标准client进行限制
eureka.server.rate-limiter-throttle-standard-clients=false

#注册服务、拉去服务列表数据的请求频率的平均值
eureka.server.rate-limiter-registry-fetch-average-rate=500

#设置信任的client list
eureka.server.rate-limiter-privileged-clients=

#在设置的时间范围类,期望与client续约的百分比
eureka.server.renewal-percent-threshold=0.85

#多长时间更新续约的阈值
eureka.server.renewal-threshold-update-interval-ms=0

#对于缓存的注册数据,多长时间过期
eureka.server.response-cache-auto-expiration-in-seconds=180

#多长时间更新一次缓存中的服务注册数据
eureka.server.response-cache-update-interval-ms=0

#缓存增量数据的时间,以便在检索的时候不丢失信息
eureka.server.retention-time-in-m-s-in-delta-queue=0

#当时间戳不一致的时候,是否进行同步
eureka.server.sync-when-timestamp-differs=true

#是否采用只读缓存策略,只读策略对于缓存的数据不会过期。
eureka.server.use-read-only-response-cache=true


################server node 与 node 之间关联的配置#####################
#发送复制数据是否在request中,总是压缩
eureka.server.enable-replicated-request-compression=false

#指示群集节点之间的复制是否应批处理以提高网络效率
eureka.server.batch-replication=false

#允许备份到备份池的最大复制事件数量。而这个备份池负责除状态更新的其他事件。可以根据内存大小,超时和复制流量,来设置此值得大小
eureka.server.max-elements-in-peer-replication-pool=10000

#允许备份到状态备份池的最大复制事件数量
eureka.server.max-elements-in-status-replication-pool=10000

#多个服务中心相互同步信息线程的最大空闲时间
eureka.server.max-idle-thread-age-in-minutes-for-peer-replication=15

#状态同步线程的最大空闲时间
eureka.server.max-idle-thread-in-minutes-age-for-status-replication=15

#服务注册中心各个instance相互复制数据的最大线程数量
eureka.server.max-threads-for-peer-replication=20

#服务注册中心各个instance相互复制状态数据的最大线程数量
eureka.server.max-threads-for-status-replication=1

#instance之间复制数据的通信时长
eureka.server.max-time-for-replication=30000

#正常的对等服务instance最小数量。-1表示服务中心为单节点
eureka.server.min-available-instances-for-peer-replication=-1

#instance之间相互复制开启的最小线程数量
eureka.server.min-threads-for-peer-replication=5

#instance之间用于状态复制,开启的最小线程数量
eureka.server.min-threads-for-status-replication=1

#instance之间复制数据时可以重试的次数
eureka.server.number-of-replication-retries=5

#eureka节点间间隔多长时间更新一次数据。默认10分钟
eureka.server.peer-eureka-nodes-update-interval-ms=600000

#eureka服务状态的相互更新的时间间隔。
eureka.server.peer-eureka-status-refresh-time-interval-ms=0

#eureka对等节点间连接超时时间
eureka.server.peer-node-connect-timeout-ms=200

#eureka对等节点连接后的空闲时间
eureka.server.peer-node-connection-idle-timeout-seconds=30

#节点间的读数据连接超时时间
eureka.server.peer-node-read-timeout-ms=200

#eureka server 节点间连接的总共最大数量
eureka.server.peer-node-total-connections=1000

#eureka server 节点间连接的单机最大数量
eureka.server.peer-node-total-connections-per-host=10

#在服务节点启动时,eureka尝试获取注册信息的次数
eureka.server.registry-sync-retries=

#在服务节点启动时,eureka多次尝试获取注册信息的间隔时间
eureka.server.registry-sync-retry-wait-ms=

#当eureka server启动的时候,不能从对等节点获取instance注册信息的情况,应等待多长时间。
eureka.server.wait-time-in-ms-when-sync-empty=0

Eureka Client 的常用配置

#该客户端是否可用
eureka.client.enabled=true

#实例是否在eureka服务器上注册自己的信息以供其他服务发现,默认为true
eureka.client.register-with-eureka=false

#此客户端是否获取eureka服务器注册表上的注册信息,默认为true
eureka.client.fetch-registry=false

#是否过滤掉,非UP的实例。默认为true
eureka.client.filter-only-up-instances=true

#与Eureka注册服务中心的通信zone和url地址
eureka.client.serviceUrl.defaultZone=http://${
    
    eureka.instance.hostname}:${
    
    server.port}/eureka/

#client连接Eureka服务端后的空闲等待时间,默认为30 秒
eureka.client.eureka-connection-idle-timeout-seconds=30

#client连接eureka服务端的连接超时时间,默认为5秒
eureka.client.eureka-server-connect-timeout-seconds=5

#client对服务端的读超时时长
eureka.client.eureka-server-read-timeout-seconds=8

#client连接all eureka服务端的总连接数,默认200
eureka.client.eureka-server-total-connections=200

#client连接eureka服务端的单机连接数量,默认50
eureka.client.eureka-server-total-connections-per-host=50

#执行程序指数回退刷新的相关属性,是重试延迟的最大倍数值,默认为10
eureka.client.cache-refresh-executor-exponential-back-off-bound=10

#执行程序缓存刷新线程池的大小,默认为5
eureka.client.cache-refresh-executor-thread-pool-size=2

#心跳执行程序回退相关的属性,是重试延迟的最大倍数值,默认为10
eureka.client.heartbeat-executor-exponential-back-off-bound=10

#心跳执行程序线程池的大小,默认为5
eureka.client.heartbeat-executor-thread-pool-size=5

# 询问Eureka服务url信息变化的频率(s),默认为300秒
eureka.client.eureka-service-url-poll-interval-seconds=300

#最初复制实例信息到eureka服务器所需的时间(s),默认为40秒
eureka.client.initial-instance-info-replication-interval-seconds=40

#间隔多长时间再次复制实例信息到eureka服务器,默认为30秒
eureka.client.instance-info-replication-interval-seconds=30

#从eureka服务器注册表中获取注册信息的时间间隔(s),默认为30秒
eureka.client.registry-fetch-interval-seconds=30

# 获取实例所在的地区。默认为us-east-1
eureka.client.region=us-east-1

#实例是否使用同一zone里的eureka服务器,默认为true,理想状态下,eureka客户端与服务端是在同一zone下
eureka.client.prefer-same-zone-eureka=true

# 获取实例所在的地区下可用性的区域列表,用逗号隔开。(AWS)
eureka.client.availability-zones.china=defaultZone,defaultZone1,defaultZone2

#eureka服务注册表信息里的以逗号隔开的地区名单,如果不这样返回这些地区名单,则客户端启动将会出错。默认为null
eureka.client.fetch-remote-regions-registry=

#服务器是否能够重定向客户端请求到备份服务器。 如果设置为false,服务器将直接处理请求,如果设置为true,它可能发送HTTP重定向到客户端。默认为false
eureka.client.allow-redirects=false

#客户端数据接收
eureka.client.client-data-accept=

#增量信息是否可以提供给客户端看,默认为false
eureka.client.disable-delta=false

#eureka服务器序列化/反序列化的信息中获取“_”符号的的替换字符串。默认为“__“
eureka.client.escape-char-replacement=__

#eureka服务器序列化/反序列化的信息中获取“$”符号的替换字符串。默认为“_-”
eureka.client.dollar-replacement="_-"

#当服务端支持压缩的情况下,是否支持从服务端获取的信息进行压缩。默认为true
eureka.client.g-zip-content=true

#是否记录eureka服务器和客户端之间在注册表的信息方面的差异,默认为false
eureka.client.log-delta-diff=false

# 如果设置为true,客户端的状态更新将会点播更新到远程服务器上,默认为true
eureka.client.on-demand-update-status-change=true

#此客户端只对一个单一的VIP注册表的信息感兴趣。默认为null
eureka.client.registry-refresh-single-vip-address=

#client是否在初始化阶段强行注册到服务中心,默认为false
eureka.client.should-enforce-registration-at-init=false

#client在shutdown的时候是否显示的注销服务从服务中心,默认为true
eureka.client.should-unregister-on-shutdown=true

猜你喜欢

转载自blog.csdn.net/weixin_38192427/article/details/113854489