注册中心 Eureka 源码解析 —— 应用实例注册发现(八)之覆盖状态

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 注册中心 Eureka 源码解析 —— 应用实例注册发现(八)之覆盖状态

本文主要基于 Eureka 1.8.X 版本

    1. 概述
    2. 2. 应用实例覆盖状态变更接口
  • 2.1 更新应用实例覆盖状态

  • 3.1 删除应用实例覆盖状态

  • 4.1 应用实例状态覆盖规则

  • 4.2 注册场景

  • 4.3 续租场景

  • 4.4 下线场景

  • 4.5 过期场景

1. 概述

本文主要分享 应用实例的覆盖状态属性

这里要注意下,不是应用实例的状态(  status ),而是覆盖状态(  overridestatus ) 。代码如下:


public class InstanceInfo {

    private volatile InstanceStatus overriddenstatus = InstanceStatus.UNKNOWN;

    // ... 省略属性和方法

}

调用 Eureka-Server HTTP Restful 接口  apps/${APP_NAME}/${INSTANCE_ID}/status 对应用实例覆盖状态的变更,从而达到主动的、强制的变更应用实例状态。注意,实际不会真的修改 Eureka-Client 应用实例的状态,而是修改在 Eureka-Server 注册的应用实例的状态

通过这样的方式,Eureka-Client 在获取到注册信息时,并且配置  eureka.shouldFilterOnlyUpInstances = true,过滤掉非  InstanceStatus.UP 的应用实例,从而避免调动该实例,以达到应用实例的暂停服务(  InstanceStatus.OUT_OF_SERVICE ),而无需关闭应用实例

因此,大多数情况下,调用该接口的目的,将应用实例状态在 (  InstanceStatus.UP ) 和 (  InstanceStatus.OUT_OF_SERVICE ) 之间切换。引用官方代码上的注释如下:

`AbstractInstanceRegistry#statusUpdate` 方法注释  Updates the status of an instance.  Normally happens to put an instance between {@link InstanceStatus#OUT_OF_SERVICE} and {@link InstanceStatus#UP} to put the instance in and out of traffic.

推荐 Spring Cloud 书籍

  • 请支持正版。下载盗版,等于主动编写低级 BUG 。
  • 程序猿DD —— 《Spring Cloud微服务实战》
  • 周立 —— 《Spring Cloud与Docker微服务架构实战》
  • 两书齐买,京东包邮。

推荐 Spring Cloud 视频

  • Java 微服务实践 - Spring Boot
  • Java 微服务实践 - Spring Cloud
  • Java 微服务实践 - Spring Boot / Spring Cloud

接口  apps/${APP_NAME}/${INSTANCE_ID}/status 实际是两个:

  • PUT  apps/${APP_NAME}/${INSTANCE_ID}/status
  • DELETE  apps/${APP_NAME}/${INSTANCE_ID}/status

下面,我们逐节分享这两接口的代码实现。

2. 应用实例覆盖状态变更接口

应用实例覆盖状态变更接口,映射  InstanceResource#statusUpdate() 方法,实现代码如下:


@PUT
@Path("status")
public Response statusUpdate(
       @QueryParam("value") String newStatus,
       @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
       @QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
   try {
       // 应用实例不存在
       if (registry.getInstanceByAppAndId(app.getName(), id) == null) {
           logger.warn("Instance not found: {}/{}", app.getName(), id);
           return Response.status(Status.NOT_FOUND).build();
       }

       // 覆盖状态更新
       boolean isSuccess = registry.statusUpdate(app.getName(), id,
               InstanceStatus.valueOf(newStatus), lastDirtyTimestamp,
               "true".equals(isReplication));

       // 返回结果
       if (isSuccess) {
           logger.info("Status updated: " + app.getName() + " - " + id
                   + " - " + newStatus);
           return Response.ok().build();
       } else {
           logger.warn("Unable to update status: " + app.getName() + " - "
                   + id + " - " + newStatus);
           return Response.serverError().build();
       }
   } catch (Throwable e) {
       logger.error("Error updating instance {} for status {}", id,
               newStatus);
       return Response.serverError().build();
   }
}
  • 调用 `PeerAwareInstanceRegistryImpl#statusUpdate(...)` 方法,更新应用实例覆盖状态。实现代码如下:
    @Override
    public boolean statusUpdate(final String appName, final String id,
                               final InstanceStatus newStatus, String lastDirtyTimestamp,
                               final boolean isReplication) {
       if (super.statusUpdate(appName, id, newStatus, lastDirtyTimestamp, isReplication)) {
           // Eureka-Server 集群同步
           replicateToPeers(Action.StatusUpdate, appName, id, null, newStatus, isReplication);
           return true;
       }
       return false;
    }
    
      - 调用父类 `AbstractInstanceRegistry#statusUpdate(…)` 方法,更新应用实例覆盖状态。

      2.1 更新应用实例覆盖状态

      调用  AbstractInstanceRegistry#statusUpdate(...) 方法,更新应用实例覆盖状态,实现代码如下:

      
        1: @Override
        2: public boolean statusUpdate(String appName, String id,
        3:                             InstanceStatus newStatus, String lastDirtyTimestamp,
        4:                             boolean isReplication) {
        5:     try {
        6:         // 获取读锁
        7:         read.lock();
        8:         // 添加 覆盖状态变更次数 到 监控
        9:         STATUS_UPDATE.increment(isReplication);
       10:         // 获得 租约
       11:         MapString, LeaseInstanceInfo gMap = registry.get(appName);
       12:         LeaseInstanceInfo lease = null;
       13:         if (gMap != null) {
       14:             lease = gMap.get(id);
       15:         }
       16:         // 租约不存在
       17:         if (lease == null) {
       18:             return false;
       19:         } else {
       20:             // 设置 租约最后更新时间(续租)
       21:             lease.renew();
       22: 
       23:             // 应用实例信息不存在( 防御型编程 )
       24:             InstanceInfo info = lease.getHolder();
       25:             // Lease is always created with its instance info object.
       26:             // This log statement is provided as a safeguard, in case this invariant is violated.
       27:             if (info == null) {
       28:                 logger.error("Found Lease without a holder for instance id {}", id);
       29:             }
       30:             //
       31:             if ((info != null) && !(info.getStatus().equals(newStatus))) {
       32:                 // 设置 租约的开始服务的时间戳(只有第一次有效)
       33:                 // Mark service as UP if needed
       34:                 if (InstanceStatus.UP.equals(newStatus)) {
       35:                     lease.serviceUp();
       36:                 }
       37:                 // 添加到 应用实例覆盖状态映射
       38:                 // This is NAC overridden status
       39:                 overriddenInstanceStatusMap.put(id, newStatus);
       40:                 // 设置 应用实例覆盖状态
       41:                 // Set it for transfer of overridden status to replica on
       42:                 // replica start up
       43:                 info.setOverriddenStatus(newStatus);
       44:                 // 设置 应用实例信息 数据不一致时间
       45:                 long replicaDirtyTimestamp = 0;
       46:                 // 设置 应用实例状态
       47:                 info.setStatusWithoutDirty(newStatus);
       48:                 if (lastDirtyTimestamp != null) {
       49:                     replicaDirtyTimestamp = Long.valueOf(lastDirtyTimestamp);
       50:                 }
       51:                 // If the replication's dirty timestamp is more than the existing one, just update
       52:                 // it to the replica's.
       53:                 if (replicaDirtyTimestamp  info.getLastDirtyTimestamp()) {
       54:                     info.setLastDirtyTimestamp(replicaDirtyTimestamp);
       55:                 }
       56:                 // 添加到 最近租约变更记录队列
       57:                 info.setActionType(ActionType.MODIFIED);
       58:                 recentlyChangedQueue.add(new RecentlyChangedItem(lease));
       59:                 // 设置 最后更新时间
       60:                 info.setLastUpdatedTimestamp();
       61:                 // 设置 响应缓存 过期
       62:                 invalidateCache(appName, info.getVIPAddress(), info.getSecureVipAddress());
       63:             }
       64:             return true;
       65:         }
       66:     } finally {
       67:         // 释放锁
       68:         read.unlock();
       69:     }
       70: }
      
    • 第 6 至 7 行 :获取读锁。在 《Eureka源码解析 —— 应用实例注册发现 (九)之岁月是把萌萌的读写锁》详细解析。
    • 第 8 至 9 行 :添加覆盖状态变更次数到监控。配合 Netflix Servo 实现监控信息采集。
    • 第 10 至 15 行 :获得租约。
    • 第 16 至 18 行 :租约不存在,返回更新失败。
    • 第 20 至 21 行 :设置租约最后更新时间( 续租 )。
    • 第 23 至 29 行 :持有租约的应用实例不存在,理论来说不会出现,防御性编程。
    • 第 31 行 :**应用实例当前状态和覆该状态不一致时才更新覆盖状态**。
    • 第 32 至 36 行 :当覆盖状态是 `InstanceStatus.UP`,设置租约的开始服务的时间戳(只有第一次有效)。
    • 第 37 至 39 行 :添加到应用实例覆盖状态映射( `overriddenInstanceStatusMap` )。此处英文 `"NAC"` 可能是 `"Network Access Control"` 的缩写,感兴趣的可以看看 《Network Access Control》 。`overriddenInstanceStatusMap` 属性代码如下:
      /**
      * 应用实例覆盖状态映射
      * key:应用实例编号
      */
      protected final ConcurrentMapString, InstanceStatus overriddenInstanceStatusMap = CacheBuilder
            .newBuilder().initialCapacity(500)
            .expireAfterAccess(1, TimeUnit.HOURS)
            .String, InstanceStatusbuild().asMap();
      
        - 有效期 1 小时。每次访问后会刷新有效期,在后文你会看到对其的访问。

        第 8 至 9 行 :添加覆盖状态变更次数到监控。配合 Netflix Servo 实现监控信息采集。

        第 16 至 18 行 :租约不存在,返回更新失败。

        第 23 至 29 行 :持有租约的应用实例不存在,理论来说不会出现,防御性编程。

        第 32 至 36 行 :当覆盖状态是  InstanceStatus.UP,设置租约的开始服务的时间戳(只有第一次有效)。

        第 40 至 43 行 :设置应用实例的覆盖状态。用于 Eureka-Server 集群同步。

        第 46 至 47 行 :设置应用实例状态。设置后,Eureka-Client 拉取注册信息,被更新覆盖状态的应用实例就是设置的状态。

        第 48 至 55 行 :设置应用实例的数据不一致时间。用于 Eureka-Server 集群同步。

        第 56 至 58 行 :添加应用实例到最近租约变更记录队列。

        第 59 至 60 行 :设置应用实例的最后更新时间(  lastUpdatedTimestamp )。 lastUpdatedTimestamp 主要用于记录最后更新时间,无实际业务用途。

        第 61 至 62 行 :设置响应缓存过期。

        第 64 行 :返回更新成功。

        第 68 行 :释放读锁。

        3. 应用实例覆盖状态删除接口

        当我们不需要应用实例的覆盖状态时,调度接口接口进行删除。关联官方  issue#89 :Provide an API to remove all overridden status。

        应用实例覆盖状态删除接口,映射  InstanceResource#deleteStatusUpdate() 方法,实现代码如下:

        
        @DELETE
        @Path("status")
        public Response deleteStatusUpdate(
               @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
               @QueryParam("value") String newStatusValue,
               @QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
           try {
               // 应用实例不存在
               if (registry.getInstanceByAppAndId(app.getName(), id) == null) {
                   logger.warn("Instance not found: {}/{}", app.getName(), id);
                   return Response.status(Status.NOT_FOUND).build();
               }
        
               // 覆盖状态删除
               InstanceStatus newStatus = newStatusValue == null ? InstanceStatus.UNKNOWN : InstanceStatus.valueOf(newStatusValue);
               boolean isSuccess = registry.deleteStatusOverride(app.getName(), id,
                       newStatus, lastDirtyTimestamp, "true".equals(isReplication));
        
               // 返回结果
               if (isSuccess) {
                   logger.info("Status override removed: " + app.getName() + " - " + id);
                   return Response.ok().build();
               } else {
                   logger.warn("Unable to remove status override: " + app.getName() + " - " + id);
                   return Response.serverError().build();
               }
           } catch (Throwable e) {
               logger.error("Error removing instance's {} status override", id);
               return Response.serverError().build();
           }
        }
        
      • 请求参数 `newStatusValue` ,设置应用实例的状态。大多数情况下,`newStatusValue` 要和应用实例实际的状态一致,因为该应用实例的 Eureka-Client 不会从 Eureka-Server 拉取到该应用状态 `newStatusValue` 。另外一种方式,不传递该参数,相当于 `UNKNOWN` 状态,这样,Eureka-Client 会主动向 Eureka-Server 再次发起注册,具体原因在 [「4.3 续租场景」] 详细解析,更加推荐的方式。
      • 调用父类 `AbstractInstanceRegistry#deleteStatusOverride(...)` 方法,删除应用实例覆盖状态。实现代码如下:
        @Override
        public boolean deleteStatusOverride(String appName, String id,
                                           InstanceStatus newStatus,
                                           String lastDirtyTimestamp,
                                           boolean isReplication) {
           if (super.deleteStatusOverride(appName, id, newStatus, lastDirtyTimestamp, isReplication)) {
               // Eureka-Server 集群同步
               replicateToPeers(Action.DeleteStatusOverride, appName, id, null, null, isReplication);
               return true;
           }
           return false;
        }
        
          - 调用父类 `AbstractInstanceRegistry#deleteStatusOverride(…)` 方法,删除应用实例覆盖状态。

          调用父类  AbstractInstanceRegistry#deleteStatusOverride(...) 方法,删除应用实例覆盖状态。实现代码如下:

          3.1 删除应用实例覆盖状态

          调用父类  AbstractInstanceRegistry#deleteStatusOverride(...) 方法,删除应用实例覆盖状态。实现代码如下:

          
            1: @Override
            2: public boolean deleteStatusOverride(String appName, String id,
            3:                                     InstanceStatus newStatus,
            4:                                     String lastDirtyTimestamp,
            5:                                     boolean isReplication) {
            6:     try {
            7:         // 获取读锁
            8:         read.lock();
            9:         // 添加 覆盖状态删除次数 到 监控
           10:         STATUS_OVERRIDE_DELETE.increment(isReplication);
           11:         // 获得 租约
           12:         MapString, LeaseInstanceInfo gMap = registry.get(appName);
           13:         LeaseInstanceInfo lease = null;
           14:         if (gMap != null) {
           15:             lease = gMap.get(id);
           16:         }
           17:         // 租约不存在
           18:         if (lease == null) {
           19:             return false;
           20:         } else {
           21:             // 设置 租约最后更新时间(续租)
           22:             lease.renew();
           23: 
           24:             // 应用实例信息不存在( 防御型编程 )
           25:             InstanceInfo info = lease.getHolder();
           26:             // Lease is always created with its instance info object.
           27:             // This log statement is provided as a safeguard, in case this invariant is violated.
           28:             if (info == null) {
           29:                 logger.error("Found Lease without a holder for instance id {}", id);
           30:             }
           31: 
           32:             // 移除 应用实例覆盖状态
           33:             InstanceStatus currentOverride = overriddenInstanceStatusMap.remove(id);
           34:             if (currentOverride != null && info != null) {
           35:                 // 设置 应用实例覆盖状态
           36:                 info.setOverriddenStatus(InstanceStatus.UNKNOWN);
           37:                 // 设置 应用实例状态
           38:                 info.setStatusWithoutDirty(newStatus);
           39:                 // 设置 应用实例信息 数据不一致时间
           40:                 long replicaDirtyTimestamp = 0;
           41:                 if (lastDirtyTimestamp != null) {
           42:                     replicaDirtyTimestamp = Long.valueOf(lastDirtyTimestamp);
           43:                 }
           44:                 // If the replication's dirty timestamp is more than the existing one, just update
           45:                 // it to the replica's.
           46:                 if (replicaDirtyTimestamp  info.getLastDirtyTimestamp()) {
           47:                     info.setLastDirtyTimestamp(replicaDirtyTimestamp);
           48:                 }
           49:                 // 添加到 最近租约变更记录队列
           50:                 info.setActionType(ActionType.MODIFIED);
           51:                 recentlyChangedQueue.add(new RecentlyChangedItem(lease));
           52:                 // 设置 最后更新时间
           53:                 info.setLastUpdatedTimestamp();
           54:                 // 设置 响应缓存 过期
           55:                 invalidateCache(appName, info.getVIPAddress(), info.getSecureVipAddress());
           56:             }
           57:             return true;
           58:         }
           59:     } finally {
           60:         // 释放锁
           61:         read.unlock();
           62:     }
           63: }
          
          • 第 7 至 8 行 :获取读锁。在 《Eureka源码解析 —— 应用实例注册发现 (九)之岁月是把萌萌的读写锁》详细解析。
          • 第 9 至 10 行 :添加覆盖状态删除次数到监控。配合 Netflix Servo 实现监控信息采集。
          • 第 11 至 16 行 :获得租约。
          • 第 17 至 19 行 :租约不存在,返回更新失败。
          • 第 21 至 22 行 :设置租约最后更新时间( 续租 )。
          • 第 24 至 30 行 :持有租约的应用实例不存在,理论来说不会出现,防御性编程。
          • 第 32 至 33 行 :移除出应用实例覆盖状态映射(  overriddenInstanceStatusMap )。
          • 第 34 行 :应用实例的覆盖状态存在才设置状态
          • 第 35 至 36 行 :设置应用实例的覆盖状态为 InstanceStatus.UNKNOWN。用于 Eureka-Server 集群同步。
          • 第 37 至 38 行 :设置应用实例的状态为  newStatus。设置后,Eureka-Client 拉取注册信息,被更新覆盖状态的应用实例就是设置的状态。
          • 第 39 至 48 行 :设置应用实例的数据不一致时间。用于 Eureka-Server 集群同步。
          • 第 49 至 51 行 :添加应用实例到最近租约变更记录队列。
          • 第 52 至 53 行 :设置应用实例的最后更新时间(  lastUpdatedTimestamp )。 lastUpdatedTimestamp 主要用于记录最后更新时间,无实际业务用途。
          • 第 54 至 55 行 :设置响应缓存过期。
          • 第 57 行 :返回更新成功。
          • 第 61 行 :释放读锁。

          4. 应用实例覆盖状态映射

          虽然我们在上面代码,使用覆盖状态(  overridestatus )设置到应用实例的状态(  status ),实际调用  AbstractInstanceRegistry#getOverriddenInstanceStatus(...) 方法,根据应用实例状态覆盖规则( InstanceStatusOverrideRule )进行计算最终应用实例的状态。实现代码如下:

          
          // AbstractInstanceRegistry.java
          protected InstanceInfo.InstanceStatus getOverriddenInstanceStatus(InstanceInfo r,
                                                                         LeaseInstanceInfo existingLease,
                                                                         boolean isReplication) {
             InstanceStatusOverrideRule rule = getInstanceInfoOverrideRule();
             logger.debug("Processing override status using rule: {}", rule);
             return rule.apply(r, existingLease, isReplication).status();
          }
          
          protected abstract InstanceStatusOverrideRule getInstanceInfoOverrideRule();
          
        • 调用 `#getInstanceInfoOverrideRule()` 方法,获取应用实例状态覆盖规则( InstanceStatusOverrideRule )。在 PeerAwareInstanceRegistryImpl 里该方法实现代码如下:
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          
          private final InstanceStatusOverrideRule instanceStatusOverrideRule;
           
          public PeerAwareInstanceRegistryImpl(
                      EurekaServerConfig serverConfig,
                      EurekaClientConfig clientConfig,
                      ServerCodecs serverCodecs,
                      EurekaClient eurekaClient
              ) {
              // ... 省略其它方法
          this.instanceStatusOverrideRule = new FirstMatchWinsCompositeRule(
              new DownOrStartingRule(),
              new OverrideExistsRule(overriddenInstanceStatusMap), 
              new LeaseExistsRule());
          }
           
          @Override
          protected InstanceStatusOverrideRule getInstanceInfoOverrideRule() {
             return this.instanceStatusOverrideRule;
          }

          public PeerAwareInstanceRegistryImpl(
                      EurekaServerConfig serverConfig,
                      EurekaClientConfig clientConfig,
                      ServerCodecs serverCodecs,
                      EurekaClient eurekaClient
              ) {
              // … 省略其它方法
          this.instanceStatusOverrideRule = new FirstMatchWinsCompositeRule(
              new DownOrStartingRule(),
              new OverrideExistsRule(overriddenInstanceStatusMap), 
              new LeaseExistsRule());
          }

          @Override
          protected InstanceStatusOverrideRule getInstanceInfoOverrideRule() {
             return this.instanceStatusOverrideRule;
          }

        • 
          com.netflix.eureka.registry.rule.InstanceStatusOverrideRule</code> ,应用实例状态覆盖规则**接口**。接口代码如下:</p>
          <pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-size: 15px; color: #3e3e3e; line-height: inherit; letter-spacing: 2px; word-spacing: 2px; background-color: #ffffff;"><code class="Java language-Java hljs" style="margin-right: 2px; margin-left: 2px; padding: 0.5em; font-size: 14px; color: #a9b7c6; line-height: 18px; border-radius: 0px; background: #282b2e; font-family: Consolas, Inconsolata, Courier, monospace; display: block; overflow-x: auto; word-spacing: 0px; letter-spacing: 0px; word-wrap: normal !important; overflow-y: auto !important;">// InstanceStatusOverrideRule.java
          public interface InstanceStatusOverrideRule {
          
               /**
               * Match this rule.
               *
               * @param instanceInfo The instance info whose status we care about. 关注状态的应用实例对象
               * @param existingLease Does the instance have an existing lease already? If so let's consider that. 已存在的租约
               * @param isReplication When overriding consider if we are under a replication mode from other servers. 是否是 Eureka-Server 发起的请求
               * @return A result with whether we matched and what we propose the status to be overriden to.
               */
               StatusOverrideResult apply(final InstanceInfo instanceInfo,
                                         final LeaseInstanceInfo existingLease,
                                         boolean isReplication);
          
          }
          
          // StatusOverrideResult.java
          public class StatusOverrideResult {
          
              public static StatusOverrideResult NO_MATCH = new StatusOverrideResult(false, null);
          
              public static StatusOverrideResult matchingStatus(InstanceInfo.InstanceStatus status) {
                  return new StatusOverrideResult(true, status);
              }
          
              // Does the rule match?
              private final boolean matches;
          
              // The status computed by the rule.
              private final InstanceInfo.InstanceStatus status;
          
              private StatusOverrideResult(boolean matches, InstanceInfo.InstanceStatus status) {
                  this.matches = matches;
                  this.status = status;
              }
          
              public boolean matches() {
                  return matches;
              }
          
              public InstanceInfo.InstanceStatus status() {
                  return status;
              }
          }
          
          • #apply(…) 方法参数  instanceInfo 代表的是关注状态的应用实例,和方法参数  existingLease里的应用实例不一定是同一个,在 「4.1.6 总结」 详细解析。
          • com.netflix.eureka.registry.rule.StatusOverrideResult ,状态覆盖结果。当匹配成功,返回  matches = true ;否则,返回  matches = false 。

          实现类关系如下

          • AsgEnabledRule ,亚马逊 AWS 专用,跳过。

          4.1.1 FirstMatchWinsCompositeRule

          com.netflix.eureka.registry.rule.FirstMatchWinsCompositeRule ,复合规则,以第一个匹配成功为准。实现代码如下:

          
          // 超过微信限制 50000 字了
          
          • rules 属性,复合规则集合。在 PeerAwareInstanceRegistryImpl 里,我们可以看到该属性为 [ DownOrStartingRule , OverrideExistsRule , LeaseExistsRule ] 。
          • defaultRule 属性,默认规则,值为 AlwaysMatchInstanceStatusRule 。
          • #apply() 方法,优先使用复合规则(  rules ),顺序匹配,直到匹配成功 。当未匹配成功,使用默认规则(  defaultRule ) 。

          4.1.2 DownOrStartingRule

          com.netflix.eureka.registry.rule.DownOrStartingRule ,匹配  InstanceInfo.InstanceStatus.DOWN 或者  InstanceInfo.InstanceStatus.STARTING 状态。实现  #apply(...) 代码如下:

          
          // 超过微信限制 50000 字了
          
          • 注意,使用的是  instanceInfo 。

          4.1.3 OverrideExistsRule

          com.netflix.eureka.registry.rule.OverrideExistsRule ,匹配应用实例覆盖状态映射(  statusOverrides ) 。实现  #apply(...) 代码如下:

          
          // 超过微信限制 50000 字了
          
          • statusOverrides 属性,应用实例覆盖状态映射。在 PeerAwareInstanceRegistryImpl 里,使用  AbstractInstanceRegistry.overriddenInstanceStatusMap 属性赋值。
          • 上文我们提到  AbstractInstanceRegistry.overriddenInstanceStatusMap 每次访问刷新有效期,如果调用到 OverrideExistsRule ,则会不断刷新。从 DownOrStartingRule 看到, instanceInfo 处于  InstanceInfo.InstanceStatus.DOWN 或者  InstanceInfo.InstanceStatus.STARTING才不会继续调用 OverrideExistsRule 匹配, AbstractInstanceRegistry.overriddenInstanceStatusMap 才有可能过期。

          4.1.4 LeaseExistsRule

          com.netflix.eureka.registry.rule.LeaseExistsRule ,匹配已存在租约的应用实例的  nstanceStatus.OUT_OF_SERVICE 或者  InstanceInfo.InstanceStatus.UP 状态。实现  #apply(...) 代码如下:

          
          // 超过微信限制 50000 字了
          
          • 注意,使用的是  existingLease ,并且非 Eureka-Server 请求。

          4.1.5 AlwaysMatchInstanceStatusRule

          com.netflix.eureka.registry.rule.AlwaysMatchInstanceStatusRule ,总是匹配关注状态的实例对象 instanceInfo )的状态。实现  #apply(...) 代码如下:

          
          // 超过微信限制 50000 字了
          
          • 注意,使用的是  instanceInfo 。

          4.1.6 总结

          我们将 PeerAwareInstanceRegistryImpl 的应用实例覆盖状态规则梳理如下:

        • 应用实例状态是**最重要**的属性,没有之一,因而在最终实例状态的计算,以**可信赖**为主。
        • DownOrStartingRule ,`instanceInfo` 处于 `STARTING` 或者 `DOWN` 状态,应用实例可能不适合提供服务( 被请求 ),考虑**可信赖**,返回 `instanceInfo` 的状态。
        • OverrideExistsRule ,当存在覆盖状态( `statusoverrides` ) ,使用该状态,比较好理解。
        • LeaseExistsRule ,来自 Eureka-Client 的请求( 非 Eureka-Server 集群请求),当 Eureka-Server 的实例状态**存在**,并且处于 `UP` 或则 `OUT_OF_SERVICE` ,保留当前状态。原因,**禁止 Eureka-Client 主动在这两个状态之间切换。如果要切换,使用应用实例覆盖状态变更与删除接口**。
        • AlwaysMatchInstanceStatusRule ,使用 `instanceInfo` 的状态返回,以保证能匹配到状态。
        • 在下文中,你会看到,`#getOverriddenInstanceStatus()` 方法会在**注册**和**续租**使用到。结合上图,我们在 「4.2 注册场景」 和 「4.3 续租场景」 也会详细解析。
        • 在下文中,你会看到,`#getOverriddenInstanceStatus()` 方法会在**注册**和**续租**使用到,方法参数 `instanceInfo` 情况如下:
            - **注册时** :请求参数 `instanceInfo` ,和 `existingLease` 的应用实例属性不相等( 如果考虑 Eureka-Server 的 `LastDirtyTimestamp` 更大的情况,则类似 **续租时的情况** ) 。 - **续租时** :使用 Eureka-Server 的 `existingLease` 的应用实例,两者相等。 - **总的来说,可以将 `instanceInfo` 理解成请求方的状态**。

            DownOrStartingRule , instanceInfo 处于  STARTING 或者  DOWN 状态,应用实例可能不适合提供服务( 被请求 ),考虑可信赖,返回  instanceInfo 的状态。

            LeaseExistsRule ,来自 Eureka-Client 的请求( 非 Eureka-Server 集群请求),当 Eureka-Server 的实例状态存在,并且处于  UP 或则  OUT_OF_SERVICE ,保留当前状态。原因,禁止 Eureka-Client 主动在这两个状态之间切换。如果要切换,使用应用实例覆盖状态变更与删除接口

            在下文中,你会看到, #getOverriddenInstanceStatus() 方法会在注册续租使用到。结合上图,我们在 「4.2 注册场景」 和 「4.3 续租场景」 也会详细解析。

            DownOrStartingRule ,

            4.2 注册场景

            
            // AbstractInstanceRegistry.java
            // 超过微信限制 50000 字了
            
            • 第 7 行 :获得已存在的租约(  existingLease ) 。
            • 第 15 行 :创建新的租约(  lease )。
            • 第 24 至 28 行 :设置应用实例的覆盖状态(  overridestatus ),避免注册应用实例后,丢失覆盖状态。
            • 第 30 至 32 行 :获得应用实例最终状态。注意下,不考虑第 9 行代码的情况, registrant 和  existingLease 的应用实例不是同一个对象。
            • 第 33 只 34 行 :设置应用实例的状态。

            4.3 续租场景

            
            // AbstractInstanceRegistry.java
            // 超过微信限制 50000 字了
            
            • 第 15 至 17 行 :获得应用实例的最终状态
            • 第 18 至 24 行 :应用实例的最终状态为  UNKNOWN,无法续约 。返回  false 后,请求方( Eureka-Client 或者 Eureka-Server 集群其他节点 )会发起注册,在 《Eureka 源码解析 —— 应用实例注册发现(二)之续租》 有详细解析。为什么会是 UNKNOWN?在 「3. 应用实例覆盖状态删除接口」 传递应用实例状态为  UNKNOWN 。
            • 第 25 至 36 行 :应用实例的状态与**最终状态**不相等,使用**最终状态**覆盖应用实例的状态。**为什么会不相等**呢?`#renew(…)` 和 `#statusUpdate(…)` 可以无锁,并行执行,如果
              • #renew(…) 执行完第 16 行代码,获取到 overriddenInstanceStatus 后,恰巧 #statusUpdate(…) 执行完更新应用实例状态 newStatus,又恰好两者不相等,使用 overriddenInstanceStatus 覆盖掉应用实例的 newStatus 状态。
              • 那岂不是覆盖状态( overriddenstatus )反倒被覆盖???不会,在下一次心跳,应用实例的状态会被修正回来。当然,如果应用实例状态如果为 UP 或者 STARTING 不会被修正,也不应该被修正。

              4.4 下线场景

              
              // AbstractInstanceRegistry.java
              // 超过微信限制 50000 字了
              

              4.5 过期场景

              同 「4.4 下线场景」 相同。

              5. 客户端调用接口

              对应用实例覆盖状态的变更和删除接口调用,点击如下方法查看,非常易懂,本文就不啰嗦了:

              • AbstractJerseyEurekaHttpClient#statusUpdate(…)
              • AbstractJerseyEurekaHttpClient#deleteStatusOverride(…)

              666. 彩蛋

              猜测覆盖状态的花费了较长时间,梳理应用实例覆盖规则耗费大量脑细胞。

              下一篇,让我鸡鸡动动的,Eureka-Server 集群同步走起!

              胖友,分享我的公众号( 芋道源码 ) 给你的胖友可好?

              注册中心 Eureka 源码解析 —— 应用实例注册发现(八)之覆盖状态

              目前在知识星球(https://t.zsxq.com/2VbiaEu)更新了如下 Dubbo 源码解析如下:01. 调试环境搭建

              02. 项目结构一览

              03. API 配置(一)之应用

              04. API 配置(二)之服务提供者

              05. API 配置(三)之服务消费者

              06. 属性配置

              07. XML 配置

              08. 核心流程一览09. 拓展机制 SPI10. 线程池11. 服务暴露(一)之远程暴露(Injvm)12. 服务暴露(二)之远程暴露(Dubbo)…

              一共 60 篇++

  • 本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 注册中心 Eureka 源码解析 —— 应用实例注册发现(八)之覆盖状态


     上一篇
    注册中心 Eureka源码解析 —— 应用实例注册发现 (九)之岁月是把萌萌的读写锁 注册中心 Eureka源码解析 —— 应用实例注册发现 (九)之岁月是把萌萌的读写锁
    精尽 Dubbo 原理与源码专栏( 已经完成 69+ 篇,预计总共 75+ 篇 ) 中文详细注释的开源项目 Java 并发源码合集 RocketMQ 源码合集 Sharding-JDBC 源码解析合集 Spring MVC 和 Secur
    2021-04-05
    下一篇 
    注册中心 Eureka 源码解析 —— 应用实例注册发现(七)之增量获取 注册中心 Eureka 源码解析 —— 应用实例注册发现(七)之增量获取
    中文详细注释的开源项目 Java 并发源码合集 RocketMQ 源码合集 Sharding-JDBC 源码解析合集 Spring MVC 和 Security 源码合集 MyCAT 源码解析合集 本文主要基于 Eureka 1.8.X
    2021-04-05