前言
Eureka与之前使用的注册中心有些差别,Eureka的重可用性在服务化场景下是亮点,本文详细总结Eureka的特性,尤其是与zk等常用组件的差别,可以帮助更好的理解spring cloud过程。阅读本文需要对注册中心有一定的了解。
Eureka调用图
Eureka调用图如下所示
上图分为两大部分
- Eureka相关:这是Eureka集群,Eureka分为两部分server和client
+ Eureka Server:服务注册server端,用于贮存注册信息,组成集群
+ Eureka client:服务注册client端,是微服务的一个进程,与Eureka server连接注册服务信息。还有一个作用是当Server组成集群时server的每个节点也会起client进程用于跟集群中其他节点通信。 - Application相关:这是微服务,也分为两种,严格的说应该是三种
- Application provider:也就是上图标的application service,这是服务的提供者
- Application consumer:也就是上图标的application client,这是服务的消费者
- Application:在很多场景下我们的服务并没有严格区分是提供者还是消费者,只是按照某些场景或者模块做的区分,但是各个模块(微服务)可能会互相调用,这时候Application其实既是提供者又是消费者。
中心server
- 内部有二层缓存(一个读一个写)机制来维护注册表,注意是缓存,所以数据是在内存中的,所以跟数据库和zk不同,这个数据不会落盘,这个很重要,数据不落盘对Eureka的部署很重要。比如目前都是云原生模式,K8S部署不落盘数据和落盘数据差别非常大。
- 只会存储服务的信息,不会存储具体的接口信息。这个也非常重要,在前面Eureka部署的文档中我们可以在管理界面看到,在管理界面中我们只能看到服务信息和instance信息,看不到具体有哪些接口注册到Eureka。所以Eureka的压力不在有多少接口,而在于有多少服务和instance,服务就是不同的微服务,instance就是相同微服务的不同机器。
client
- client端本地会缓存注册表,如果某个服务没有缓存会向Server获取。
- 通过心跳机制和server同步客户端状态。server和client有租约机制,client超过租约时间没注册server会认为client故障。
动作
-
服务注册:client把自身注册到服务中心,还有自身的元数据信息,可通过/eureka/apps/服务名 查看
-
服务续约:client 30s发送一次心跳来续约,server 90s没收到会将实例从注册表中删除。详细时间:
服务续约任务的调用间隔时间,默认为30秒 eureka.instance.lease-renewal-interval-in-seconds=30 服务失效的时间,默认为90秒。 eureka.instance.lease-expiration-duration-in-seconds=90 ```
-
服务剔除:server 90s没收到回将实例从注册表中删除
-
服务下线:client关闭时向server发送取消请求,server删除此client实例信息,不会自动完成,需要配置
-
获取信息:client每30s向server获取一次注册表缓存到本地,分为全量和增量拉取
启用服务消费者从注册中心拉取服务列表的功能 eureka.client.fetch-registry=true 设置服务消费者从注册中心拉取服务列表的间隔 eureka.client.registry-fetch-interval-seconds=30
远程调用:client+feign+ribbon共同完成
client从缓存拿注册表,如果没有则去server取,得到远程实例信息
feign拼装HTTP调用信息
ribbon做多instance的负载均衡
自我保护
- Eureka Server 在一定的 90s 内没有接收到某个微服务实例的心跳,会注销该实例,如果是网络分区等情况就很危险
- Server 如果在15分钟内超过85%的客户端节点都没有正常心跳,那么Eureka就人为客户端与注册中心出现了网络故障,server进入自我保护机制。
- Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
- Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
- 当网络稳定时,当前实例新的注册信息会被同步到其它节点中
- 客户端心跳恢复时Eureka 会自动退出自我保护机制。
- eureka.server.enable-self-preservation=true 自我保护机制的开关
集群原理
- 配置集群必须不同ip
- 相互之间不区分主节点和从节点,所有节点都是平等,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务
- server也是client,所以几个server组成集群,要彼此互相注册
- Client 在向某个 Eureka 注册时,如果发现连接失败,则会自动切换至其它节点。只要有一台 Eureka Server 还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。从这里可以看出client只会选择一个server进行注册,注册后由server根据配置中集群的信息用同样的方式注册到其他server上。
- client启动后注册一个server,之后如果这个Eureka Server挂了,才会切换Eureka Server,在当前使用的Eureka Server挂掉之前,不会切换。
- 扩缩容
- Eureka Server在启动后会调用EurekaClientConfig.getEurekaServerServiceUrls来获取所有的Peer节点,并且会定期更新。默认情况下是通过本节点中配置文件里配置的集群信息获取peer节点,所以配置很重要。
- 定期更新频率可以通过eureka.server.peerEurekaNodesUpdateIntervalMs配置,默认10分钟。
- EurekaClientConfig.getEurekaServerServiceUrls默认从配置文件中拿,也可以override getEurekaServerServiceUrls方法,提供自己的实现,比如通过数据库读取Eureka Server列表并动态更新,这样就可以实现Eureka server的动态扩缩容,K8S部署就像部署无状态节点一样。后面会专门开篇详细介绍此部分改动。
- 还可以配置zone和region,实现zone优先
- 数据同步
+ server启动时(服务启动前就获取,这也是为什么启动就会有一次报错)默认从其他节点获取注册表,从其他节点中的一个获取
+ 通过互相同步数据来做到数据同步,异步同步,达到最终一致。 当自己的节点发生变化就会同步,A-B-C同步过程如下:
- 如果client注册到A,A的节点就发生了变化,A会同步(这里不是像数据库一样的通过log之类的做同步,而是A中的client通过相同的方式注册到B)到B,B的节点也变化了,但是是因为A同步过来导致的变化,所以B不会再同步给其他
- 通过上面可知,要想数据一致必须A同步给B和C
- 注意不是想象的数据同步,而是跟client注册到A一样,A的client用相同的http的方式注册到BC。区别:Client-A: Header信息中isReplication=true, A-BC: isReplication=false,防止循环复制
- 那是不是集群中必须互相注册呢,A必须注册BC,B必须AC,C必须AB呢?已验证。配置文件中配置了同步到谁,就只会同步到谁- 数据冲突
- lastDirtyTimestamp 是注册中心里面服务实例的一个属性,表示此服务实例最近一次变更时间。靠这个时间确定新旧
- 如果新,让另一个更新,如果旧自己更新
- 数据冲突
K8S部署指南
- 1双节点自动配置
+ 采用脚本方案,启动查数据库,设置defaultZone,但是此种方式挂了重启重新分配ip时集群其他节点不会动态更新
+ 方案抛弃 - 2采用配置文件方式,使用服务名来配置集群,每个节点是一个服务,client也配置服务名
+ 使用K8S服务名配置,设置eureka为DNS注册方式
+ eureka可以动态加载配置文件,可实现扩缩容,但是扩缩容后微服务不感知,除非也修改微服务的配置文件 - 3重新实现eureka获取defaultZone的方式
+ 重新实现获取defaultZone,让其读取数据库,实现动态加载
+ 此部分后面会开篇详细介绍
参考
- 1比较全面 https://blog.csdn.net/qwe86314/article/details/94552801
- 2扩缩容部分 https://zhuanlan.zhihu.com/p/88385121
- 3数据冲突部分 https://zhuanlan.zhihu.com/p/96396725
- 4这里提到了一点有个peerEurekaNodes,也就是每个eureka中都维护了一份集群列表: https://www.cnblogs.com/zhixiang-org-cn/p/11716916.html
* 而且代码中也看到新注册流程中保存了本地后就会注册到其他节点,而且是拿到所有集群列表排除自己其他全部同步
* 拿集群列表如果是配置文件就是去配置文件中拿,会动态更新,完全按照配置文件中来的 - 5这里提及了同步时候是注册方式而不是数据同步: https://blog.csdn.net/thesummers/article/details/109650232