阅读 74

Netflix Eureka 原理探究

---

1. web.xml

<!-- 在web 项目启动的时候会执行 ,在 eureka core 里--> 
<listener>
    <listener-class>com.netflix.eureka.EurekaBootStrap</listener-class>
</listener>
复制代码

web.xml 中配置了好多个 fileter 和 listener

  • com.netflix.eureka.EurekaBootStrap :负责 eureka-server 的初始化。
  • filter : 任何一个请求,都会经过 filter
  • com.netflix.eureka.StatusFilter :负责状态相关的处理逻辑
  • com.netflix.eureka.ServerRequestAuthFilter :对请求进行授权认证
  • com.netflix.eureka.RateLimitingFilter :限流相关逻辑(关注如何实现限流的),默认是不开启的,需要自己进行参数设置
<!-- Uncomment this to enable rate limiter filter.
  <filter-mapping>
    <filter-name>rateLimitingFilter</filter-name>
    <url-pattern>/v2/apps</url-pattern>
    <url-pattern>/v2/apps/*</url-pattern>
  </filter-mapping>
  -->
复制代码
  • com.netflix.eureka.GzipEncodingEnforcingFilter :处理压缩相关,编码相关,只会对某些请求生效
<filter-mapping>
    <filter-name>gzipEncodingEnforcingFilter</filter-name>
    <url-pattern>/v2/apps</url-pattern>
    <url-pattern>/v2/apps/*</url-pattern>
  </filter-mapping>
复制代码
  • jersey 是一个全局的,对所有应用都生效,这是一个 Restful 风格的框架
<filter-mapping>
    <filter-name>jersey</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
复制代码
  • welcome-list 启动的首页,也就是我们看到的 eureka server 注册中心的那个页面
 <welcome-file-list>
    <welcome-file>jsp/status.jsp</welcome-file>
  </welcome-file-list>
复制代码

2.Eureka Server 启动

从 web.xml 中可以看出 EurekaBootStrap 这个类的 contextInitialized 就是启动入口

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);
        }
    }
复制代码

2.1 initEurekaServerContext() 基本流程解析

  • 首先会构建一个 DefaultEurekaServerConfig 这个类,去执行内部的 init() 方法,进行配置文件的加载
  • 从上一步设置的环境信息中,获取到环境配置,将其设置到配置管理类 ConfigurationManager 中
  • 找到配置中写好的配置类的名称 eureka-server 的文件名
  • 通过 ConfigurationManager 进行配置文件的加载,会拿到上面设置的配置文件的名称 eureka-server ,将其和 .properties 进行拼接组成完成的配置文件名称 eureka-server.properties ,调用相关API,对其内部配置的属性信息进行封装(这个文件默认是空的,但在我们进行获取值的时候,会进行默认值的填充),会将返回的参数 Properties 对象中的内容放到 ConfigurationManagaer 对象中进行统一的管理
  • EurekaServerConfig 这个接口提供了很多获取配置参数的方法,基本上 eureka-server.properties 中设置的参数,都可以从这个接口中找到方法去进行获取,这里由 DefaultEurekaServerConfig 这个类实现了这个接口
  • 通过 DynamicPropertyFactory 这个类去获取到 ConfigurationManager 中封装的 eureka-server.properties 中的配置信息,之所以通过这个类是因为 ConfigManagerConfig 在获取完配置信息之后,会将配置信息赋值给 DynaminPropertyFactory 然后通过一系列的 getXXXProperty() 方法获取到对应的配置信息,这里采用的是硬编码的形式,没有采用通过常量去获取key 对应的 value 的实现形式,并且,对于每个 key 值来说,若为空的话,都进行了默认值的设置,也可以理解为面向对象获取配置参数
  • 采用接口获取参数值这样更加的面向对象一些,可以学习和采用。

2.2 以构造者模式将 eureka server 作为一个 eureka client

  • 一个eureka server 之所以可以向另外的 eureka server 进行注册,达到组成一个集群的目的,本质上是因为 eureka server 将自己也作为了一个 eureka client 客户端,所有的参数信息交由 ApplicationInfoManager 对象进行通过管理
  • 首先会根据是否是云环境来进行配置文件的读取,采用的方式和初始化服务上下文是基本一致的,读取的是 eureka-client.properties 配置文件,由 DynamicPropertyFactory 进行获取,也是面向接口以硬编码形式进行所有参数值的获取
  • 然后会通过构造器模式(InstanceInfo 有一个 Builder 内部类),根据从配置文件中读取到的配置,进行 InstanceConfig 对象的构建,封装了一些 eureka server 作为一个实例对象所需要的参数信息
  • 最后将 InstanceInfo 对象交由 ApplicationConfigManager 进行统一管理

2.3 初始化 eureka client

  • 通过 DiscoveryClient 进行 eurekaClient 的初始化

  • 首先会将之前读取到的 eureka-client.properties 中的信息,赋值给内部的一些变量

  • 然后根据是否需要 注册 抓取注册表 初始化不同的参数,如果不需要的话,将一些属性设置为 null ,进行资源的释放

  • 构建周期性调度的线程池

  • 构建抓取注册表的线程池

  • 构建进行心跳续约的线程池

    • 这几个线程池的最大线程数都是5个
  • 构建 EurekaTransport ,主要就是在初始化 httpClient 对象,用于抓取和注册

  • 然后根据配置,去执行注册表的抓取,若设置了不进行抓取,就不会进行抓取,如果设置了抓取,但是抓取失败的话,会有一个读取备份的操作

  • 接着会初始化调度任务,根据我们的配置,配置了抓取注册表的话,就会初始化抓取注册表的任务,配置了向eureka 服务进行注册,发送心跳续约的话,就会初始化心跳的任务,还会设置 实例 的副本,并且设置实例的状态监听,如果我们配置了需要对状态进行监听的话,就对监听器进行注册

  • 后面会根据 实例副本 进行对调度任务的启动,主要是进行集群间的操作同步,比如服务上线,下线,续约等等操作。

2.4 服务上下文

  • 首先是会构建一个可以感知集群注册表的: PeerAwareInstanceRegistry 类,
  • 然后将构建 PeerEurekaNodes 这个类,用来获取集群中的集群信息
  • 然后就是会构建一个 EurekaServerContext 的服务上下文对象,并将这个对象交给 EurekaServerContextHolder ,方便别的地方随时获取到 eureka server 的上下文对象,主要会进行集群状态的更新并基于集群信息获取注册表
  • 通过 registry.syncUp 从任意的节点中获取注册表信息,将拉取到的注册表信息加入到自己本地,如果没有拉取到的话,会默认最多进行5次重试且每次重试时间间隔为30秒
  • 处理一些善后,加一些监控信息等

3. Eureka Server 主流程图

eureka server 主流程 (2).png

4. Eureka Client 启动

4.1 基本流程图

eureka client.png

4.2 eureka client 注册流程

  • 实际上他的注册流程是在构建 EurekaClient 时,有一个 InstanceInfoReplicator 负责实例副本信息的,就在上图的 第六个步骤之后 ,初始化网络通信组件也是给 eureka client 进行注册使用的。
  • 通过 clientConfig**.getInitialInstanceInfoReplicationIntervalSeconds()** 获取到默认值,40s
  • 通过调用 InstanceInfoReplicatorstart 方法,将自己作为一个线程,扔到一个调度线程池里,默认 40s 之后执行,因为是个线程,所以主要的实现逻辑都在 run 方法里
  • 通过 eureka client 实例的子类 discoveryClient 调用 register 方法进行注册
  • 这里也就用到了上图 第七个步骤 初始化的 http 组件,因为 eureka 采用的是 jersey 作为 restful 框架,所以核心的话,还是调用的 AbstractJersey2EurekaHttpClient 这个类进行的注册,主要就是设置一下路径和一些属性,以 json 格式,post 请求方法,将数据信息给 eureka server 。

** **

4.2.1 注册流程简单图解

eureka client 注册.png

4.3 eureka server 接收 eureka client 的注册

  • 对于 jersey 框架来说, resource 包就相当于我们 spring mvc 的 controller ,我们通过单元测试,进行 debug 流程
  • 接收注册的话在 ApplicationsResource 类,调用了 ApplicationResource 中的addInstance 方法
  • 进入方法之后是一串对与参数的校验,一个好的代码,也就是防御式编程,能够抵御别人胡乱传递的参数,但源码中的这种方式不推荐,可以考虑单独抽取一个方法进行实现。
  • 接着就是获取数据中心,根据不同的数据中心执行不同的操作,这里采用的是硬编码的形式,我们可以考虑通过配置文件设置参数,标记是否是 aws 云环境,用工厂或者策略模式构建不同的对象。
  • 调用 PeerAwareInstanceRegistry 这个可以感觉实例信息的类的 registry 方法,进行注册,调用这个方法实际上还是会调用父类 AbstractInstanceRegistry 的注册方法。
  • 采用读写锁的形式,在注册的时候,加上读锁,使其可以同时注册多个,服务注册表采用的是 ConcurrentHashMap>> 的数据结构。
  • 每次来新的服务进行注册的时候,都会对这个实例设置相关的 租约信息 ,设置各种属性值和状态,并且会将注册过的服务,加入到一个 recentRegisteredQueue 队列中去。
  • 对于最近更新的服务实例,会加入到一个 recentlyChangedQueue 队列中,然后释放锁。
  • 最后返回 204 ,这里出现了 magic number 记得规避。

4.4 eureka 控制台数据展示

当我们完成注册之后,在我们 eureka 的控制台页面的就能看到相应的信息

  • 首先会通过eureka server 启动的时候的 hoder 对象获取到服务的上下文对象
  • 从上下问对象中获取到 registry 注册表中获取信息
  • 遍历注册表,封装成 Application 对象,一个 Application 对象代表一个服务实例,给 jsp 页面展示

4.5 eureka 完整注册流程图

eureka 的服务注册的流程.png

5. 抓取注册表

eureka client 第一次启动的时候,需要从 eureka server 中抓取全量的注册表信息,在本地进行缓存,之后会每个 30秒 从 eureka server 抓取增量的注册表信息,跟本地的缓存进行合并

5.1 eureka client 发送抓取请求

  • 通过调用 fetchRegistry 方法,对一些参数进行判断,看是否要进行全量注册表的抓取

  • 若需要全量抓取注册表,则调用 getAndStoreFullRegistry 方法

    • 通过jersey 调用 apps 接口,获取到全量的实例信息
    • 将获取到的实例信息封装在本地缓存 AtomicRefrence

5.2 eureka server 响应抓取请求

  • eureka server 实现了一套多级缓存的机制:

  • 首先,根据 ResponseCache 类中的 getValue 方法,获取注册表信息

    • 若允许使用 只读缓存 ,则直接从 只读缓存中拿(readOnlyCacheMap ,若只读缓存中没有,则从 读写缓存中拿(readWriteCacheMap
    • 如果读写缓存中也没有的话,则会通过 PeerAwareInstanceRegistry 中获取全部的注册表
    • 若不允许使用 只读缓存 ,则直接从读写缓存中拿,若读写缓存中没有的话,则直接拉取全部的注册表
5.2.1 响应流程图

eureka server 响应抓取流程.png

5.3 缓存过期

  1. 主动过期

当有新的服务实例发生注册、下线、故障的时候,就会去刷新 readWriteCacheMap,设置相对应 key 值使其过期。

在 addInstance 的注册方法中,有一个 invaldate 方法,会将 readWriteCacheMap 中的缓存都设置为过期。

  1. 定时过期

在我们构建 readWriteCacheMap 读写缓存的时候,设置了一个 responseCacheAutoExpirationInSeconds 属性,默认值是 180 ,也就是180秒之后会进行过期

this.readWriteCacheMap =
                CacheBuilder.newBuilder().initialCapacity(1000)
                        .expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
    // 默认值 180s
    configInstance.getIntProperty(
                namespace + "responseCacheAutoExpirationInSeconds", 180).get();
复制代码
  1. 被动过期

被动过期是相对于 readOnlyCacheMap 只读缓存设置的,设置了 30 秒执行一次调度任务,将缓存状态设置为失效状态

if (shouldUseReadOnlyResponseCache) {
            timer.schedule(getCacheUpdateTask(),
                    new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
                            + responseCacheUpdateIntervalMs),
                    responseCacheUpdateIntervalMs);
        }
复制代码
  1. 引申出来一个问题,当实例注册、下线、故障的时候,要调用这个服务的其他服务,可能要隔 30秒之后,才能感知到,因为这里在获取注册表的时候,有一个多级缓存的机制,最近是30秒才会去更新缓存,后台有一个调度任务,每30秒会执行一次,主要功能就是同步读写缓存和只读缓存的数据

eureka server 缓存过期.png

5.4 增量注册表抓取

在 eureka client 启动的时候,会 初始化调度任务 ,如果需要抓取注册表的话,

会有一个 30秒( getRegistryFetchIntervalSeconds ) 一次的调度任务执行,进行增量注册表的抓取,另外就是在初始化调度任务之前,如果配置了需要抓取注册表的话,会先走一个 抓取注册表的方法,在方法内部判断了是要进行全量注册表的抓取还是增量注册表的抓取

  1. 他主要有两个入口,一个是通过 设置了允许抓取注册表的fetchRegistry方法 ,方法内部判断是进行全量抓取还是增量抓取,另一个是在 初始化调度任务的时候,初始化了一个刷新缓存的调度任务,每30秒调度一次 ,其实这两个入口最后调用的都是同一个方法。
  2. 通过调用 apps/delta 这个路径,以 ALL_APPLICATION_DELTA 为 key 进行增量注册表的抓取,这个也是会走二级缓存,不一样的地方就是这个读写缓存中如果没有的话,会通过 recentlyChangedQueue 获取数根据,构建这个的时候,构建了一个调度任务,每30秒执行一次检查, 去除这个队列中,时间超过3分钟的数据 ,也就是说,这个队列中存放了 最近三分钟的 有变化的服务实例。
  3. 在获取到值之后,会计算一个 hash 值,放到服务实例信息中,当 eureka client 获取到响应之后,会将远程获取到的和本地缓存中的值,进行合并,合并完毕之后也会计算一个 hash 值,对象两端的 hash 值是否相等,如果不相等的话,代表在其中的某个环节上出现了问题,所以进行 全量注册表的抓取 并和本地缓存进行同步。

其实在 eureka server 适合我们学习的就是

    • 注册表的二级缓存机制
    • recentlyChangeQueue 更新队列的使用,将有变化的加入到这个队列中,设置定期调度,只保留最近三分钟的数据。
    • 双端计算 hash 值 ,在服务端计算一次,在客户端计算一次,通过 hash 值判断数据是否相同,不相同则代表在某个环节出现了问题,进行一次全量表的抓取

eureka server 增量抓取注册表.png

6. 服务续约

eureka client 需要每隔一段时间,向 eureka server 发送心跳,就是告诉 eureka server 自己还活着,在 eureka 中,心跳叫做 lease 续约

在 eureka client 启动的时候,会初始化一个线程池,每隔30秒会调用 HeartbeatThread 里的 renew 续约方法,通过调用 apps/appName/Id ,进行续约

主要就是在 InstanceResouce 的 renewLease 方法,通过更改注册表中的 Lease 对象中的 ** lastUpdateTimestamp 属性来实现续约

//     duration 默认为 90秒
public void renew() {
        lastUpdateTimestamp = System.currentTimeMillis() + duration;
    }
复制代码

7.服务下线和服务摘除

如果某个服务需要停机,或者重启,eureka client 要关闭的话,需要自己去调用 EurekaClient 的 shutdown 方法,将服务实例停止。

  • 首先就是调用 shutdown 方法,取消掉 eureka client 启动的时候,启动的各种调度器和线程池;

  • 然后通过调用 unregister 方法,调用 InstanceResouce.cancelLease 方法:

    • 这个方法首先会将 注册表中的这个 client从注册表中移除掉
    • 将这个实例的信息,加入到取消队列中 (recentCanceledQueue)
    • 更新 Lease 对象中的下线时间
    • 会将这个实例,加入到变更对边中(recentlyChangedQueue)
    • 失效掉读写缓存
  • 在 eureka client 启动的时候,会构建一个 30秒执行一次的调度任务,主要是用来同步 readWriteCacheMapreadOnlyCacheMap 缓存的值,因此在其他客户端在进行增量注册表拉取的时候,就会发现只读缓存、读写缓存中都没有值,就会去读 recentlyChangedQueue 队列,recentlyChangedQueue 队列中又保存了之前那个实例下线的操作,所以,当这个实例下线的时候,相关的注册表信息,也就从别的服务实例上剔除了

eureka client 服务下线.png

8.服务实例的故障感知以及服务实例摘除

很多时候,可能并不是我们去手动将服务下线,而是服务自身出现问题,这时就不会调用 shutdown 方法,也不会去发送请求下线服务实例,这个时候的话,就看 eureka 自己的故障感知的机制以及服务实例摘除的机制

eureka 靠的是心跳,来判断是否是挂掉的,如果一个服务挂掉了,那么肯定是不会在发送心跳,如果在一段时间内 eureka server 没有接收到某个服务实例的心跳,就会认为这个服务实例,已经宕机了。

通过调用 registry.openForTraffic(applicationInfoManager, registryCount) 开始感知故障实例,进行移除

  1. 首先,会在 eureka server 启动的时候,调用 openForTraffic 方法,初始化一个调度任务,每 60秒执行一次
  2. 会获取到一个补偿时间,这个时间主要是用来针对在 gc 期间无法进行续约等操作或者服务器时钟出现了问题,会计算一个当前时间和上一次执行时间的差值,来作为补偿时间
  3. 拿到补偿时间之后会调用 evict 方法,获取到全部的实例信息,进行遍历,获取到每个服务实例的租约对象
  4. 根据每个服务实例的租约对象计算是否过期,这里的话 有一个bug在我们进行续约的时候,会更新最后更新时间为当前时间 + duration 的值,也就是当前时间+90秒,但在我们计算是否过期的时候,是用当前时间 - 最后一次更新时间 + duration + 补偿时间 ,补偿时间在正常状态下是0,也就是做了一个 duration * 2 的操作,也就是说,在正常情况下应该是90秒过期,但是因为这个 bug ,现在是 180 秒后才会过期,这时一个 bug
  5. 当我们判断出来是否过期之后,将过去的数据加入到集合中,这里我们采取的并不是全部一次性过期,而是采用部分随机过期的方法,每次只能过期几个值
        int registrySize = (int) getLocalRegistrySize();
        // 20 * 0。85 =  17
        int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold()); // 百分之85
        // 20 - 17 = 3,
        int evictionLimit = registrySize - registrySizeThreshold; // 计
        // 取最小值,3
        int toEvict = Math.min(expiredLeases.size(), evictionLimit);
复制代码

通过随机算法,每个只能过期集合中的部分值,调用 internalCancel 服务下线那一套,从注册表中移除,加入到取消队列,加入到最近变更队列,失效缓存信息。

eureka server 服务故障感知.png

9. 网络故障时的自我保护

假如说,有20个服务实例,结果在1分钟之后,只有8个服务实例保持了心跳,这时候是不是会将那剩余的12个服务实例都进行摘除呢 ?这个时候,很可能是 eureka sevrer 自身的网络故障,导致接收不到心跳,就导致 eureka server 本地没有更新服务实例的心跳,所以 eureka server 在一段时间内,超过一定比例的服务实例都没有发送心跳,这时会认为自己故障了,进入一个保护机制,从此之后就不会在摘除任何服务实例

  • 在 eureka server 启动的时候,有一个从相邻注服务实例拉取注册表的步骤,在这个步骤中会会将拉取到的注册表和自己本地的信息,进行对比,如果有自己本地不存在的话,会将其注册到自己本地,完成之后,会返回l拉取到的注册表的数量。
  • 获取到上次同步到的数量,进入 服务实例故障感知的阶段 ,首先会先将拉取到的服务实例个数 * 2,因为默认是30秒执行一次续约操作,这个是以一分钟为一个周期,所以要将这个个数 * 2,也就是说,如果有10个服务实例,在一分钟内默认情况下,就要有20次心跳。(**当然直接这么 * 2,是不正确的,因为我们可能会设置间隔时间,好像后面1.9有修复),**然后拿着这个值 * serverConfig.getRenewalPercentThreshold(默认是0.85),计算出来一个预期的每分钟要收到的心跳数****numberOfRenewsPerMinThreshold
// 因为每30秒会发送一次心跳,下面计算值得时候是计算的一分钟的,所以直接*2
this.expectedNumberOfRenewsPerMin = count * 2; // 直接 * 2
 // 计算希望一分钟要接收到多少次心跳的值,至少有 count*2*85%的响应
this.numberOfRenewsPerMinThreshold =
    (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()); // 配置值默认0。85
复制代码
  • 然后就是每分钟的服务实例的故障感知了,在下面代码中,如果返回 false 的话,后面的服务实例移除操作就不会在走了
if (!isLeaseExpirationEnabled()) { // 是否允许主动删除掉故障的服务实例,这边跟自我保护机制相关
            // todo
            logger.debug("DS: lease expiration is currently disabled.");
            return;
 }
// 被调用
public boolean isLeaseExpirationEnabled() {
        if (!isSelfPreservationModeEnabled()) {
            // 是否启用了自我保护机制,默认是 true
            // The self preservation mode is disabled, hence allowing the instances to expire.
            return true;
        }
        // numberOfRenewsPerMinThreshold 我期望的是一分钟有多少心跳发送过来
        // getNumOfRenewsInLastMin 获取到上一分钟,所有服务实例,一共发送过来多少次心跳 renewsLastMin.getCount();
        // 每次一个心跳过来,都会更新 renewsLastMin,用来统计一分钟的心跳个数
        // 如果上一分钟的心跳次数 大于 我期望的个数,就返回 true ,可以去清除过期实例
        // 如果 不大于我期望的个数,此时就返回 false , 不会去清除过期实例

        /**
         *  numberOfRenewsPerMinThreshold 怎么算
         *  eureka server 启动的时候,会初始化一次这个值
         */
        return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
    }
复制代码
  • 上述代码的话,也是比较简单:

    • 首先的话,是检查参数,是否允许开启自我保护机制
    • numberOfRenewsPerMinThreshold ,这个的话就是在初始化阶段计算出来的 预期的一分钟内的心跳个数
    • getNumOfRenewsInLastMin ,这个的话是一分钟内收到的心跳个数,这个是个技术亮点
class MeasuredRate {
 public synchronized void start() {
        if (!isActive) { // 每分钟调度一下
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    try {
                        // Zero out the current bucket.
                        // 保留 last 最后一次
                        // 将 当前设置为 0
                        // 也就是 last 用来读取,保留上次心跳个数
                        // current 用来跟新
                        lastBucket.set(currentBucket.getAndSet(0));
                    } catch (Throwable e) {
                        logger.error("Cannot reset the Measured Rate", e);
                    }
                }
            }, sampleInterval, sampleInterval);

            isActive = true;
        }
    }   
}
复制代码

简单来说,这个类就行设置了一个定时调度任务,每1分钟调度一次,将这一分钟的数据赋值给 lastBucker 用来给外界读取,然后将 currentBucket 设置为0,可以借鉴这种在本地更新固定时间内数据的写法

    • 之后就是比较,如果上一分钟的心跳数 > 预期的一分钟的心跳数,那么服务正常
    • 如果上一分钟的心跳数 < 预期的一分钟的心跳数,那么服务就是不正常的
    • 正常的话,继续走实例过期摘除功能,不正常的话,就会触发自我保护机制,不进行服务实例的摘除
  • 另外的话,在这初始化注册表对象的时候,构建了一个周期调度任务,每15分钟执行一次,更新 一分钟内预期的心跳个数

  • 在服务注册的时候, 预期个数+2 ,在服务下线的时候 预期个数-2 ,但在服务故障的时候,没有找到 - 2 的代码,预计可能还是个 Bug

  • jsp 页面上,当 eureka server 进入保护模式的话,那句话是来自于一个方法,这个方法的判断逻辑就是上面说的那个,用上一分钟收到的心跳个数 和 预期一分钟收到的心跳个数做对比

eureka server 网络故障感知.png

10.eureka server 集群间注册表的同步

eureka server 在启动的时候,会将自己也作为一个 eureka client ,所以 eureka server 之间才可以组成集群进行相互注册。

  • 在启动 eureka server 的时候,会初始化一个 PeerEurekaNodes 用来处理 eureka server 的集群的服务实例信息,并将其加入到 服务上下文对象中,进行初始化。
  • 会有一个 start 的方法,构建了一个单线程的线程池,这个调度任务就负责,更新集群的状态信息,将配置文件中配置的 eureka server 信息,构建成一个一个的 PeerEurekaNodes 实例对象,会讲自身排除在外,每10分钟调度一次。
  • 然后会调用 syncUp 方法,从任意一个节点中拉取注册表缓存在自己本地,如果获取到的为0的话,为进行5次重试,每次重试的间隔为30秒。
  • 当然,每次在进行服务的上线,下线,心跳,故障的时候都会进行 eureka server 之间的同步
  • 注册的时候会调用 addInstance 方法,在将服务实例信息加入到 注册表 之后,会调用 replicateToPeers 进行集群间的请求同步
  • 说白了就是把配置文件中配置的地址,去除自身以后,挨个的调用相关(上线,下线,心跳,故障)的 API。
  • 当然为了防止出现服务注册间的死循环,会通过 isReplication 参数去控制,默认的时候是 false ,但在同步 的时候会将值设置为true,这样别的 eureka server 在收到同步请求的时候,不会在将其同步到其他服务实例上面,防止出现死循环的情况

eureka server 集群机制.png

文章分类
后端
文章标签