当前位置: 移动技术网 > IT编程>开发语言>Java > Eureka服务注册中心原理总结笔记

Eureka服务注册中心原理总结笔记

2020年07月09日  | 移动技术网IT编程  | 我要评论

部分内容也有参考

微服务注册中心 Eureka 架构深入解读

深入学习 Eureka 原理

SpringCloud 注册中心 Eureka 集群是怎么保持数据一致的?

一、简介

Eureka跟Zookeeper一样可以用作注册中心。

1.组件

1.1.1 Eureka Server

服务注册中心

1.启动后,从其他节点拉取服务注册信息

2.运行过程中,定时运行 evict 任务,剔除没有按时 renew 的服务(包括非正常停止和网络故障的服务)。

3.运行过程中,接收到的 register、renew、cancel 请求,都会同步至其他注册中心节点。

Eureka Server中的服务注册表中将会存储所有可用服务节点的信息

服务节点的信息可以在界面中直观的看到.

1.1.2 Eureka Client

java客户端,用于简化Eureka Server的交互,简单粗暴的说就是业务系统中引用的一个依赖。

具备一个内置的、使用轮询(round-robin)负载复法的负载均衡器。

它又分为两种角色,一般情况下一个服务节点是同时具有这两种角色

服务提供者

1.启动后,向注册中心发起 register 请求,注册服务

2.在运行过程中,定时向注册中心发送 renew 心跳,证明“我还活着”。

3.停止服务提供者,向注册中心发起 cancel 请求,清空当前服务注册信息。

服务消费者

1.启动后,从注册中心拉取服务注册信息

2.在运行过程中,定时更新服务注册信息。

3.服务消费者发起远程调用:
a> 服务消费者(北京)会从服务注册信息中选择同机房的服务提供者(北京),发起远程调用。只有同机房的服务提供者挂了才会选择其他机房的服务提供者(青岛)。
b> 服务消费者(天津)因为同机房内没有服务提供者,则会按负载均衡算法选择北京或青岛的服务提供者,发起远程调用。

在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒),

如果Eureka Server在多个心跳周期内没有接收到某 Eureka Client将会从服务注册表中把这个服务节点移除(默认90)

2.与ZK的区别

  Zookeeper Eureka
设计原则 CP AP
优点 数据强一致 服务高可用
缺点 网络分区会影响 Leader 选举,超过阈值后集群不可用

服务节点间的数据可能不一致;

Client-Server 间的数据可能不一致;

适用场景 单机房集群,对数据一致性要求较高 云机房集群,跨越多机房部署;对注册中心服务可用性要求较高

ZK是满足CP,既是一致性和分区容错性。ZK不是高可用的。

不高可用的场景举例:当master节点因为网络故障与其他节点失去取系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长, 30 ~ 120s,且选举期间整个集群都是不可用的,这就导致在选举期间注册服务瘫痪。

Eureka满足AP,既是高可用和分区容错。Eureka不是强一致性的。

Eureka是靠replicate相当于靠复制来同步数据的,只满足数据的最终一致性。

 3. Eureka Server服务处理

register

                * 保存实例信息到注册表
                * 添加事件到更新队列以供客户端增量同步
                * 清空L2
                * 更新阀值供剔除服务使用
                * 同步实例信息至对等节点

renew

                * 更新实例的最近续约时间(lastUpdateTimestamp)
                * 同步实例信息至对等节点

cancel

                * 从注册表删除实例信息
                * 添加事件到更新队列以供客户端增量同步
                * 清空L2
                * 更新阀值供剔除服务使用
                * 同步实例信息至对等节点

二、Eureka Server的服务剔除机制及自我保护

1. Eureka Server重要配置

eureka.instance.lease-expiration-duration-in-seconds:Server 至上一次收到 Client 的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该 Instance。

默认为90秒;

EurekaServer 一定要设置 eureka.server.eviction-interval-timer-in-ms 否则这个配置无效

如果该值太大,则很可能将流量转发过去的时候,该instance已经不存活了;

如果该值设置太小了,则instance则很可能因为临时的网络抖动而被摘除掉;

该值至少应该大于leaseRenewalIntervalInSeconds

 

eureka.server.enable-self-preservation:是否开启自我保护模式,默认为true

eureka.server.renewal-percent-threshold: 0.85:阈值因子,默认是0.85,如果阈值比最小值大,则自我保护模式开启

eureka.server.eviction-interval-timer-in-ms:Server 清理无效节点的时间间隔,默认60000毫秒,即60秒。

eureka.server.useReadOnlyResponseCache:是否使用 readOnlyCacheMap,默认为true。

eureka.server.responseCacheUpdateIntervalMs:readWriteCacheMap 更新至 readOnlyCacheMap 周期,默认30s。

eureka.server.responseCacheAutoExpirationInSeconds:Server 缓存readWriteCacheMap失效时间,缓存默认180s。

2.剔除服务

Eureka Server 提供了服务剔除的机制,用于剔除没有正常下线的服务。 

服务的剔除包括三个步骤

1.判断是否满足服务剔除的条件

2.找出过期的服务

3.执行剔除

2.1.1 判断是否满足服务剔除的条件

有两种情况可以满足服务剔除的条件:

1.关闭了自我保护。

则统统认为是 Eureka Client 的问题,把没按时续约的服务都剔除掉(这里有剔除的最大值限制)

2.如果开启了自我保护,需要进一步判断是 Eureka Server 出了问题,还是 Eureka Client 出了问题,如果是 Eureka Client 出了问题则进行剔除。

 自我保护阈值是区分 Eureka Client 还是 Eureka Server 出问题的临界值:如果超出阈值就表示大量服务可用,少量服务不可用,则判定是 Eureka Client 出了问题。如果未超出阈值就表示大量服务不可用,则判定是 Eureka Server 出了问题
 
自我保护阈值的计算:

  • 自我保护阈值 = 服务总数 * 每分钟续约数 * 自我保护阈值因子。
  • 每分钟续约数 =(60S/ 客户端续约间隔)

最后自我保护阈值的计算公式为:

自我保护阈值 = 服务总数 * (60S/ 客户端续约间隔) * 自我保护阈值因子。
 
举例:如果有 100 个服务,续约间隔是 30S,自我保护阈值 0.85。

自我保护阈值 =100 * 60 / 30 * 0.85 = 170。

如果上一分钟的续约数 =180>170,则说明大量服务可用,是服务问题,进入剔除流程;

如果上一分钟的续约数 =150<170,则说明大量服务不可用,是注册中心自己的问题,进入自我保护模式,不进入剔除流程。

2.1.2 找出过期的服务

遍历所有的服务,判断上次续约时间距离当前时间大于阈值就标记为过期。并将这些过期的服务保存到集合中。

2.1.3 执行剔除

在剔除服务之前先计算剔除的数量,然后遍历过期服务,通过洗牌算法确保每次都公平的选择出要剔除的任务,最后进行剔除。

执行剔除服务后:

1.删除服务信息,从 registry 中删除服务。

2.更新队列,将当前剔除事件保存到更新队列中。

3.清空二级缓存,保证数据的一致性。

实现过程参考 AbstractInstanceRegistry.evict() 方法。 

2、自我保护机制

如果在15分钟内超过 85% 的客户端节点都没有正常的心跳,那么 Eureka 就认为客户端与注册中心出现了网络故障(比如网络故障或频繁的启动关闭客户端),Eureka Server 自动进入自我保护模式.

进入自我保护模式后:

1.Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务

2.Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)

3.当它收到心跳数据恢复到阈值以上时,该Eureka Server节点会自动退出自我保护模式。当前实例新的注册信息会被同步到其它节点中..

这种设计是对网络故障的安全保护,不盲目注销可能健康的服务实例。

如果想禁用自我保护机制,在Spring Cloud中可以使用eureka.server.enable-self-preservation = false

不推荐禁用,建议修改注销实例的时间就行了 eureka.instance.lease-expiration-duration-in-seconds

三、Eureka Server的集群数据同步机制

Eureka-Server 集群不区分主从节点或者 Primary & Secondary 节点,所有节点相同角色( 也就是没有角色 ),完全对等,是peer to peer模式。

没有一致性算法,全靠复制,是最终一致性。

Eureka-Client 可以向任意 Eureka-Client 发起任意读写操作,Eureka-Server 将操作复制到另外的 Eureka-Server 以达到最终一致性。注意,Eureka-Server 是选择了 AP 的组件。

1.触发同步的时间点

启动时和运行中。

Eureka Server 本身依赖了 Eureka Client,也就是每个 Eureka Server 是作为其他 Eureka Server 的 Client。

Eureka Server 启动时,调用 PeerAwareInstanceRegistryImpl#syncUp() 方法,请求其他 Eureka Server 集群的另外一个节点,获取注册的服务信息,然后复制到其他 peer 节点。

若调用 #syncUp() 方法,未获取到应用实例,则 Eureka-Server 会有一段时间( 默认:5 分钟,可配 )不允许被 Eureka-Client 获取注册信息,避免影响 Eureka-Client 。

Eureka-Server 运行中接收到 Eureka-Client 的 Register、Heartbeat、Cancel、StatusUpdate、DeleteStatusOverride 操作,在自身节点的执行后,固定间隔( 默认值 :500 毫秒,可配 )向 Eureka-Server 集群内其他节点同步( 准实时,非实时 ),循环集群内每个节点,调用 #replicateInstanceActionsToPeers(…) 方法,发起同步操作。

2.数据同步死循环处理

如果自己的信息变更是另一个Eureka Server同步过来的,这是再同步回去的话就出现数据同步死循环了。

Eureka Server 在执行复制操作的时候,使用 HEADER_REPLICATION 这个 http header 来区分普通应用实例的正常请求,说明这是一个复制请求,这样其他 peer 节点收到请求时,就不会再对其进行复制操作,从而避免死循环。

3.数据冲突处理

数据冲突,比如 server A 向 server B 发起同步请求,如果 A 的数据比 B 的还旧,B 不可能接受 A 的数据,那么 B 是如何知道 A 的数据是旧的呢?这时 A 又应该怎么办呢?

数据的新旧一般是通过版本号来定义的,Eureka 是通过 lastDirtyTimestamp 这个类似版本号的属性来实现的。

lastDirtyTimestamp 是注册中心里面服务实例的一个属性,表示此服务实例最近一次变更时间。

比如 Eureka Server A 向 Eureka Server B 复制数据,数据冲突有2种情况:

(1)A 的数据比 B 的新,B 返回 404,A 重新把这个应用实例注册到 B。

(2)A 的数据比 B 的旧,B 返回 409,要求 A 同步 B 的数据。

preview

还有一个重要的机制:hearbeat 心跳,即续约操作,来进行数据的最终修复,因为节点间的复制可能会出错,通过心跳就可以发现错误,进行弥补。 

四、Eureka Server的数据存储结构

既然是服务注册中心,必然要存储服务的信息,我们知道 ZK 是将服务信息保存在树形节点上。

 1.Eureka 的数据存储结构

Eureka Server相当于有三级缓存了

2.registry

服务注册和取消是直接操作registry,它相当于是实时更新的。类 AbstractInstanceRegistry 成员变量,UI 端请求的是这里的服务注册信息。

rigistry 本质上是一个双层的 ConcurrentHashMap,存储在内存中的。

第一层的 key 是spring.application.name,value 是第二层 ConcurrentHashMap;

第二层 ConcurrentHashMap 的 key 是服务的 InstanceId,value 是 Lease 对象;

Lease 对象包含了服务详情和服务治理相关的属性。

3.readWriteCacheMap

(Guava Cache/LoadingCache)

实时更新,类 ResponseCacheImpl 成员变量,缓存时间 180 秒。

删除readWriteCacheMap缓存的触发点:

1.Eureka Client 发送 register、renew 和 cancel 请求并更新 registry 注册表之后,删除readWriteCacheMap缓存;

2.Eureka Server 自身的 Evict Task 剔除服务后,删除readWriteCacheMap缓存;

3.readWriteCacheMap缓存本身设置了 guava 的失效机制,隔一段时间(180 秒)后自己自动失效;

加载readWriteCacheMap级缓存的触发点:

1.Eureka Client 发送 getRegistry 请求后,如果readWriteCacheMap缓存中没有,就触发 guava 的 load,即从 registry 中获取原始服务信息后进行处理加工,再加载到readWriteCacheMap缓存中。

2.Eureka Server 每30s更新readOnlyCacheMap缓存的时候,如果readWriteCacheMap缓存没有数据,也会触发 guava 的 load。

4.readOnlyCacheMap

(ConcurrentHashMap)

周期更新,类 ResponseCacheImpl 成员变量,

默认情况下,定时任务每30s从 readWriteCacheMap 更新,

Eureka client 默认从这里更新服务注册信息,可配置直接从 readWriteCacheMap 更新

更新readOnlyCacheMap缓存的触发点:

Eureka Server 内置了一个 TimerTask,定时任务(每30s)将二级缓存中的数据同步到一级缓存(这个动作包括了删除和加载)。

关于缓存的实现参考 ResponseCacheImpl 

 五、Eureka Client服务获取机制

1.重要配置项

eureka.instance.lease-renewal-interval-in-seconds:Client 发送心跳给 Server 端的频率,默认为30秒。如果该instance实现了 HealthCheckCallback,并决定让自己 unavailable 的话,则该 Instance 也不会接收到流量。

eureka.client.registry-fetch-interval-seconds:Client 间隔多久去拉取服务注册信息,默认为30秒,对于 api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒。

eureka.client.registryFetchIntervalSeconds:Client 增量更新周期,默认30s(正常情况下增量更新,超时或与 Server 端不一致等情况则全量更新)。

ribbon.ServerListRefreshInterval:Ribbon 更新周期,默认30s。

2.拉取流程

EurekaClient 第一次全量拉取,定时增量拉取应用服务实例信息,保存在缓存中:

EurekaClient 增量拉取失败,或者增量拉取之后对比 hashcode 发现不一致,就会执行全量拉取,这样避免了网络某时段分片带来的问题;

对于服务调用,如果涉及到 ribbon 负载均衡,那么 ribbon 对于这个实例列表也有自己的缓存,这个缓存定时从 EurekaClient 的缓存更新;

3.默认配置下服务消费者最长感知时间 

 

正常上线

数据流:readWrite -> readOnly -> Client -> Ribbon

预估:30(readOnly) + 30(Client) + 30(Ribbon) = 90s

正常下线

数据流:readWrite -> readOnly -> Client -> Ribbon

预估:30(readOnly) + 30(Client Fetch) + 30(Ribbon) = 90s

服务正常下线(kill或kill -15杀死进程)会给进程善后机会,DiscoveryClient.shutdown() 将向 Server 更新自身状态为 DOWN,然后发送 DELETE 请求注销自己,registry 和 readWriteCacheMap 实时更新,故UI将不再显示该服务实例。

SpringBoot 下线是否会默认调用 DiscoveryClient.shutdown()?

非正常下线

预估:90(LeaseExpiration)*2 + 30(readOnly) + 30(Client Fetch) + 30(Ribbon) = 270s

服务非正常下线(kill -9杀死进程或进程崩溃)不会触发 DiscoveryClient.shutdown() 方法,Eureka Server 将依赖每60s清理超过90s未续约服务从 registry 和 readWriteCacheMap 中删除该服务实例。

 4.Zuul配合Eureka 、ribbon

Eureka Server 为注册中心,Zuul 相对于 Eureka Server 来说是 Eureka Client,Zuul 会把 Eureka Server 端服务列表缓存到本地,并以定时任务的形式更新服务列表,同时 Zuul 通过本地列表发现其它服务,使用 Ribbon 实现客户端负载均衡。 

重试依赖 

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

重试配置

#(是否所有操作都重试,若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

 

本文地址:https://blog.csdn.net/jy02268879/article/details/107198965

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网