部分内容也有参考
SpringCloud 注册中心 Eureka 集群是怎么保持数据一致的?
Eureka跟Zookeeper一样可以用作注册中心。
服务注册中心
1.启动后,从其他节点拉取服务注册信息。
2.运行过程中,定时运行 evict 任务,剔除没有按时 renew 的服务(包括非正常停止和网络故障的服务)。
3.运行过程中,接收到的 register、renew、cancel 请求,都会同步至其他注册中心节点。
Eureka Server中的服务注册表中将会存储所有可用服务节点的信息
服务节点的信息可以在界面中直观的看到.
java客户端,用于简化Eureka Server的交互,简单粗暴的说就是业务系统中引用的一个依赖。
具备一个内置的、使用轮询(round-robin)负载复法的负载均衡器。
它又分为两种角色,一般情况下一个服务节点是同时具有这两种角色
服务提供者
1.启动后,向注册中心发起 register 请求,注册服务
2.在运行过程中,定时向注册中心发送 renew 心跳,证明“我还活着”。
3.停止服务提供者,向注册中心发起 cancel 请求,清空当前服务注册信息。
服务消费者
1.启动后,从注册中心拉取服务注册信息
2.在运行过程中,定时更新服务注册信息。
3.服务消费者发起远程调用:
a> 服务消费者(北京)会从服务注册信息中选择同机房的服务提供者(北京),发起远程调用。只有同机房的服务提供者挂了才会选择其他机房的服务提供者(青岛)。
b> 服务消费者(天津)因为同机房内没有服务提供者,则会按负载均衡算法选择北京或青岛的服务提供者,发起远程调用。
在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒),
如果Eureka Server在多个心跳周期内没有接收到某 Eureka Client将会从服务注册表中把这个服务节点移除(默认90)
Zookeeper | Eureka | |
设计原则 | CP | AP |
优点 | 数据强一致 | 服务高可用 |
缺点 | 网络分区会影响 Leader 选举,超过阈值后集群不可用 |
服务节点间的数据可能不一致; Client-Server 间的数据可能不一致; |
适用场景 | 单机房集群,对数据一致性要求较高 | 云机房集群,跨越多机房部署;对注册中心服务可用性要求较高 |
ZK是满足CP,既是一致性和分区容错性。ZK不是高可用的。
不高可用的场景举例:当master节点因为网络故障与其他节点失去取系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长, 30 ~ 120s,且选举期间整个集群都是不可用的,这就导致在选举期间注册服务瘫痪。
Eureka满足AP,既是高可用和分区容错。Eureka不是强一致性的。
Eureka是靠replicate相当于靠复制来同步数据的,只满足数据的最终一致性。
* 保存实例信息到注册表
* 添加事件到更新队列以供客户端增量同步
* 清空L2
* 更新阀值供剔除服务使用
* 同步实例信息至对等节点
* 更新实例的最近续约时间(lastUpdateTimestamp)
* 同步实例信息至对等节点
* 从注册表删除实例信息
* 添加事件到更新队列以供客户端增量同步
* 清空L2
* 更新阀值供剔除服务使用
* 同步实例信息至对等节点
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。
Eureka Server 提供了服务剔除的机制,用于剔除没有正常下线的服务。
服务的剔除包括三个步骤
1.判断是否满足服务剔除的条件
2.找出过期的服务
3.执行剔除
有两种情况可以满足服务剔除的条件:
1.关闭了自我保护。
则统统认为是 Eureka Client 的问题,把没按时续约的服务都剔除掉(这里有剔除的最大值限制)
2.如果开启了自我保护,需要进一步判断是 Eureka Server 出了问题,还是 Eureka Client 出了问题,如果是 Eureka Client 出了问题则进行剔除。
自我保护阈值是区分 Eureka Client 还是 Eureka Server 出问题的临界值:如果超出阈值就表示大量服务可用,少量服务不可用,则判定是 Eureka Client 出了问题。如果未超出阈值就表示大量服务不可用,则判定是 Eureka Server 出了问题。
自我保护阈值的计算:
最后自我保护阈值的计算公式为:
自我保护阈值 = 服务总数 * (60S/ 客户端续约间隔) * 自我保护阈值因子。
举例:如果有 100 个服务,续约间隔是 30S,自我保护阈值 0.85。
自我保护阈值 =100 * 60 / 30 * 0.85 = 170。
如果上一分钟的续约数 =180>170,则说明大量服务可用,是服务问题,进入剔除流程;
如果上一分钟的续约数 =150<170,则说明大量服务不可用,是注册中心自己的问题,进入自我保护模式,不进入剔除流程。
遍历所有的服务,判断上次续约时间距离当前时间大于阈值就标记为过期。并将这些过期的服务保存到集合中。
在剔除服务之前先计算剔除的数量,然后遍历过期服务,通过洗牌算法确保每次都公平的选择出要剔除的任务,最后进行剔除。
执行剔除服务后:
1.删除服务信息,从 registry 中删除服务。
2.更新队列,将当前剔除事件保存到更新队列中。
3.清空二级缓存,保证数据的一致性。
实现过程参考 AbstractInstanceRegistry.evict() 方法。
如果在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 集群不区分主从节点或者 Primary & Secondary 节点,所有节点相同角色( 也就是没有角色 ),完全对等,是peer to peer模式。
没有一致性算法,全靠复制,是最终一致性。
Eureka-Client 可以向任意 Eureka-Client 发起任意读写操作,Eureka-Server 将操作复制到另外的 Eureka-Server 以达到最终一致性。注意,Eureka-Server 是选择了 AP 的组件。
启动时和运行中。
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(…)
方法,发起同步操作。
如果自己的信息变更是另一个Eureka Server同步过来的,这是再同步回去的话就出现数据同步死循环了。
Eureka Server 在执行复制操作的时候,使用 HEADER_REPLICATION
这个 http header 来区分普通应用实例的正常请求,说明这是一个复制请求,这样其他 peer 节点收到请求时,就不会再对其进行复制操作,从而避免死循环。
数据冲突,比如 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 的数据。
还有一个重要的机制:hearbeat 心跳,即续约操作,来进行数据的最终修复,因为节点间的复制可能会出错,通过心跳就可以发现错误,进行弥补。
既然是服务注册中心,必然要存储服务的信息,我们知道 ZK 是将服务信息保存在树形节点上。
Eureka Server相当于有三级缓存了
服务注册和取消是直接操作registry,它相当于是实时更新的。类 AbstractInstanceRegistry 成员变量,UI 端请求的是这里的服务注册信息。
rigistry 本质上是一个双层的 ConcurrentHashMap,存储在内存中的。
第一层的 key 是
spring.application.name
,value 是第二层 ConcurrentHashMap;第二层 ConcurrentHashMap 的 key 是服务的 InstanceId,value 是 Lease 对象;
Lease 对象包含了服务详情和服务治理相关的属性。
(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。
(ConcurrentHashMap)
周期更新,类 ResponseCacheImpl 成员变量,
默认情况下,定时任务每30s从 readWriteCacheMap 更新,
Eureka client 默认从这里更新服务注册信息,可配置直接从 readWriteCacheMap 更新
更新readOnlyCacheMap缓存的触发点:
Eureka Server 内置了一个 TimerTask,定时任务(每30s)将二级缓存中的数据同步到一级缓存(这个动作包括了删除和加载)。
关于缓存的实现参考 ResponseCacheImpl
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。
EurekaClient 第一次全量拉取,定时增量拉取应用服务实例信息,保存在缓存中:
EurekaClient 增量拉取失败,或者增量拉取之后对比 hashcode 发现不一致,就会执行全量拉取,这样避免了网络某时段分片带来的问题;
对于服务调用,如果涉及到 ribbon 负载均衡,那么 ribbon 对于这个实例列表也有自己的缓存,这个缓存定时从 EurekaClient 的缓存更新;
数据流: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 中删除该服务实例。
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
如对本文有疑问, 点击进行留言回复!!
利用python将Mysql信息以Excel文件并作为邮件附件发送
springmvc+mybaits+mysql上传表情Incorrect string value: ‘\xF0\x9F\xA4\xB4\xF0\x9F...‘ for
SpringCloud Greenwich集成Seata1.2.0详解说明(原创by ulwfcyvi)
mybatis generator生成代码库 与指定的库不一致 为其他库的同名表
网友评论