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
的客户端服务启动的时候,它会发起一个 Http
的 POST
请求到 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 Client
和 Eureka 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 Server
在 90s
内没有接收到某个微服务实例的心跳,它会将实例从其注册表中删除。但是,在微服务架构下服务之间通常都是跨进程调用,网络通信往往面临各种问题,比如微服务状态正常,网络分区故障,导致此实例被注销。为了解决这个问题,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
提供了 Region
和 Zone
两个概念来进行分区,这两个概念均来自于亚马逊的 AWS
:
region
:可以理解为地理上的不同区域,比如亚洲地区,中国区或者深圳等等。没有具体大小的限制。根据项目具体的情况,可以自行合理划分region
zone
:可以简单理解为region
内的具体机房,比如说region
划分为深圳,然后深圳有两个机房,就可以在此region
之下划分出zone1、zone2
两个zone
上图中的 us-east-1c、us-east-1d、us-east-1e
就代表了不同的 Zone
。Zone
内的 Eureka Client
优先和 Zone
内的 Eureka Server
进行心跳同步,同样调用端优先在 Zone
内的 Eureka Server
获取服务列表,当 Zone
内的 Eureka Server
挂掉之后,才会从别的 Zone
中获取信息
Eureka
的工作流程
Eureka Server
启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过Replicate
同步注册表,每个Eureka Server
都存在独立完整的服务注册表信息Eureka Client
启动时根据配置的Eureka Server
地址去注册中心注册服务Eureka Client
会每30s
向Eureka Server
发送一次心跳请求,证明客户端服务正常- 当
Eureka Server
在90s
内没有收到Eureka Client
的心跳,注册中心则认为该节点失效,会注销该实例 - 单位时间内
Eureka Server
统计到有大量的Eureka Client
没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端 - 当
Eureka Client
心跳请求恢复正常之后,Eureka Server
自动退出自我保护模式 Eureka Client
定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地- 服务调用时,
Eureka Client
会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存 Eureka Client
获取到目标服务器信息,发起服务调用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
数据存储层。当获取缓存时判断缓存中是否没有数据,如果不存在此数据,则通过CacheLoader
的load
方法去加载,加载成功之后将数据放入缓存,同时返回数据。它的缓存过期时间是180s
,当服务下线,注册,状态改变等都会来清除缓存中的数据
Eureka Client
的缓存机制
- 在
Eureka Client
启动时会全量拉取注册表信息,启动完成后,会每隔30s
从Eureka Server
增量拉取信息,并保存在本地的缓存中 - 在
Eureka Client
增量拉取失败或者增量拉取之后对比HashCode
发现不一致,就会执行全量拉取,同样会更新保存到本地的缓存 - 对于服务的调用,如果涉及到
Ribbon
的负载均衡,而Ribbon
对于这个服务实例列表也有自己的缓存,这个缓存每隔30s
从Eureka Client
的缓存更新
这么多的缓存机制可能就会造成一些问题,一个服务启动后可能最长需要 90s
才能被其它服务感知到:
- 首先,
Eureka Server
会维护每30s
更新的响应缓存 Eureka Client
对已经获取到的注册信息也做了30s
缓存- 负载均衡组件
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