Eureka 部分机制记录

599 阅读6分钟

Eureka 部分机制记录

背景

eureka 分布式服务,注册中心,满足 CAP 中的 AP。通过一些技术细节内幕,保证了其可用性和分区容错性。
最近也是在看 eureka 相关源码,针对其中的部分,在这里做一些记录。
针对以下的一些点

(1)eureka server启动:注册中心
(2)eureka client启动:服务实例
(3)服务注册:map数据结构
(4)eureka server集群:注册表的同步,多级队列的任务批处理机制
(5)全量拉取注册表:多级缓存机制
(6)增量拉取注册表:一致性hash比对机制
(7)心跳机制:服务续约,renew
(8)服务下线:cancel
(9)服务故障:expiration,eviction
(10)自我保护:自动识别eureka server出现网络故障了

Eureka部分机制的实现原理

Eureka 作为 spring-cloud 所选中的注册中心,其特性和突出的点,不可赘述,下边,将会针对其中的一些点,做一些记录。

先来一张整体的流程和架构图

非全面机制细节呈现图,主要是一个大致的流程运行图。

从图中,可以看到,一个service (eurka-client)启动,会向 eureka-server进行服务注册,将自己作为provider ,注册到 eureka-server 中,并定时的,从 server 端进行 续约 (心跳机制)eureka 中叫做 renewLease,且定时的从 server 端拉取 注册表内容,用来保证自己本地维护的注册表信息完全和一致。

###服务注册

eureka 服务注册比较简单,也没有太多的内容在里边,主要是 client 初始化的时候,调度 registry 通过 jersey 进行 http 的请求,然后 server 端,进行接收之后,将元信息(metadata) 保存到 server 端的内存 map 之中,并且通status 控制台,可以看元数据的信息。下边是个简单的服务注册流程

###client 注册启动过程

作为一个 eureka-client ,从整体运行图上,我们也可以看到,会进行,一些心跳的续约,还有注册表的拉取更新,下边,从 client 的启动整体启动过程,做一下梳理

Eureka client ,具体的实现在 DiscoveryClient 这个类中,具体的 代码细节,可在源码中,自行查阅,以下是一张 client 注册启动的流程图

可以看到,在注册启动的时候,初始化了 3 个线程池任务用来进行之前说的,续约,心跳,注册表拉取功能.

刚才也说了,client 会定时的调度,会拉取刷新注册表,在 client 中,会存在 2 中拉取机制

  • 增量同步
  • 全量同步

下边是关于 client 每 30s 一次的更新流程,在 server 端,会有一个队里,保存最近 3 分钟之内增量变更的注册表

client 端通过 serve 端的返回,然后进行和 本地进行对比,进行增删改操作,最后通过本地的最新的全量注册表,计算hash 值,在和服务端返回的 全量注册表的 hash 值,做一致性 hash 校验,如果不一致,在进行一次全量的拉取,保证其一致性

server 端的 3 层缓存机制

server 端启动与集群部署

先看下 server 端的工程目录架构,可以看到,eureka-server 的项目中,除了 resources中的一些配置之外 只有 web.xml这样一个工程目录

具体的启动逻辑,就在 web.xml 的配置之中 ,listener core 工程中的 EurekaBootStrap

  <!-- web 应用初始化 ,一些初始化代码
      eureka server 的初始化
  -->
  <listener>
    <listener-class>com.netflix.eureka.EurekaBootStrap</listener-class>
  </listener>

启动大致流程,如下图所示, EurekaBootStrap 实现了 ServletContextListener 接口,在 contextInitialized 实现方法中,实现了 server 短的环境,配置初始化,和启动。

   public void contextInitialized(ServletContextEvent event) {
        try {
            initEurekaEnvironment();
            initEurekaServerContext();

            ServletContext sc = event.getServletContext();
            sc.setAttribute(EurekaServerContext.class.getName(), serverContext);
        } catch (Throwable e) {
            logger.error("Cannot bootstrap eureka server :", e);
            throw new RuntimeException("Cannot bootstrap eureka server :", e);
        }
    }

为了保证服务的高可用,eureka-server 端肯定是需要通过集群部署的。在初始的整体架构图中,可以看到,当 server 启动的时候,也会作为eureka-client 向另外的 server 进行注册,并同步拉取注册表,这样,就保证了不同 server 之间的信息同步。

下边将针对服务状态维护,展开一些深入的探讨。

服务状态维护

服务下线

关于service 的一些实例信息,是维护在 server 端的,一旦当有服务关机了,势必是要通知到 eureka-server,进行服务的下线,然后从注册表中摘除,并且清除缓存,最终完成了服务的线下,具体流程,如下图

从这张图上,也可以看到,在 server 端,会维护一个 recentlyChangedQueue,顾名思义 在这个队列中,会维护最近 3 分钟之内的变更记录,然后由一个定时任务,维护最近 3 分钟的变更,清理之前的变更记录。这个任务,在 构架实例表的时候,就已经构建了,后台运行。并且也会清理缓存,使其失效,这边会有一个 readWriteCacheMap 这样的一个读写缓存,稍后会针对这个换错,做下详细说明。

server 端自动故障感知&服务实例摘除&网络故障,自我保护机制

除了一些正常的关机之外,比如当业务service 宕机了,或者 oom 等异常,导致服务没有自发的进行 shutdown 操作,下线服务,这样,服务依旧会存在 server 端的注册表中,那么这时候,就需要有机制,来保证故障自动感知,并将故障服务,从服务清单中,进行摘除,最后,在由 client 端的定期的拉取,做到同步感知。一些细节流程如下图所示

在这个流程中,有几个关键因素:

  • 续约心跳(client)
  • 心跳时间(server 端)
  • 补偿时间(防止由于 server 端 GC 之类的stw ,导致的时间延长)
  • 上次的心跳次数

并且内,为了防止由于一些续约心跳上报时候的服务网络故障,导致的上报异常,如果开启了自我保护,那么会在服务摘除的时候,进行自我保护机制的判断,在里边会预留一定的 buffer,防止因异常而全部摘除

并且在服务摘除的时候,也会进行一定比例的随机摘除,非一次性全部摘除。

eueka server 之间状态同步任务批处理机制

eureka-server 是集群部署的,我们也知道,client 进行注册,续约,下线,都只会随机的同步到一台server 机器上,必不可少的就是 server 端的状态互相同步

查看源码。也可以知道,每一次的注册,下线,或者服务剔除,都会向其他 peeerNode 进行数据同步。在同步过程中,eureka 采取了3 重队列,然后通过批处理的方式,一次性提交请求。当服务多的时候,注册或者下线,不会有太多的请求,按时心跳续约,这是很频繁的,势必是要采取批量请求的方式,进行服务之间的状态同步

具体的同步流程,如下图所示

具体的逻辑代码实现在这个地方 eureka-core 中

难免疏漏,不足之处,烦请指出,欢迎支持沟通交流