Eureka 源碼解析 —— 應用實例註冊發現(八)之覆蓋狀態

摘要: 原創出處 http://www.iocoder.cn/Eureka/instance-registry-override-status/ 「芋道源碼」歡迎轉載,保留摘要,謝謝!

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

  • 1. 概述
  • 2. 應用實例覆蓋狀態變更接口

  • 3. 應用實例覆蓋狀態刪除接口
  • 4. 應用實例覆蓋狀態映射


  • 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 書籍


    接口 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: Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
    12: Lease<InstanceInfo> 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 ConcurrentMap<String, InstanceStatus> overriddenInstanceStatusMap = CacheBuilder
      .newBuilder().initialCapacity(500)
      .expireAfterAccess(1, TimeUnit.HOURS)
      .<String, InstanceStatus>build().asMap();
      • 有效期 1 小時。每次訪問後會刷新有效期,在後文你會看到對其的訪問。

    • 第 40 至 43 行 :設置應用實例的覆蓋狀態。用於 Eureka-Server 集羣同步。


    • 第 46 至 47 行 :設置應用實例狀態。設置後,Eureka-Client 拉取註冊信息,被更新覆蓋狀態的應用實例就是設置的狀態。
    • 第 48 至 55 行 :設置應用實例的數據不一致時間。用於 Eureka-Server 集羣同步。
    • 第 56 至 58 行 :添加應用實例到最近租約變更記錄隊列。
    • 第 59 至 60 行 :設置應用實例的最後更新時間( lastUpdatedTimestamp )。lastUpdatedTimestamp 主要用於記錄最後更新時間,無實際業務用途。
    • 第 61 至 62 行 :設置響應緩存過期。
    • 第 64 行 :返回更新成功。
    • 第 68 行 :釋放讀鎖。

    3. 應用實例覆蓋狀態刪除接口

    當我們不需要應用實例的覆蓋狀態時,調度接口接口進行刪除。關聯官方 issue#89Provide 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(...) 方法,刪除應用實例覆蓋狀態。


    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: Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
    13: Lease<InstanceInfo> 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,
    Lease<InstanceInfo> 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 裏該方法實現代碼如下:

      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;
      }

    4.1 應用實例狀態覆蓋規則

    com.netflix.eureka.registry.rule.InstanceStatusOverrideRule ,應用實例狀態覆蓋規則接口。接口代碼如下:


    // 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 Lease<InstanceInfo> 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複合規則,以第一個匹配成功爲準。實現代碼如下:


    public class FirstMatchWinsCompositeRule implements InstanceStatusOverrideRule {
    /
    * 複合規則集合
    /
    private final InstanceStatusOverrideRule[] rules;
    /
    默認規則
    */
    private final InstanceStatusOverrideRule defaultRule;
    private final String compositeRuleName;
    public FirstMatchWinsCompositeRule(InstanceStatusOverrideRule... rules) {
    this.rules = rules;
    this.defaultRule = new AlwaysMatchInstanceStatusRule();
    // Let's build up and "cache" the rule name to be used by toString();
    List<String> ruleNames = new ArrayList<>(rules.length+1);
    for (int i = 0; i < rules.length; ++i) {
    ruleNames.add(rules[i].toString());
    }
    ruleNames.add(defaultRule.toString());
    compositeRuleName = ruleNames.toString();
    }
    @Override
    public StatusOverrideResult apply(InstanceInfo instanceInfo,
    Lease<InstanceInfo> existingLease,
    boolean isReplication) {
    // 使用複合規則,順序匹配,直到匹配成功
    for (int i = 0; i < this.rules.length; ++i) {
    StatusOverrideResult result = this.rules[i].apply(instanceInfo, existingLease, isReplication);
    if (result.matches()) {
    return result;
    }
    }
    // 使用默認規則
    return defaultRule.apply(instanceInfo, existingLease, isReplication);
    }
    @Override
    public String toString() {
    return this.compositeRuleName;
    }
    }
    • 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(...) 代碼如下:


    @Override
    public StatusOverrideResult apply(InstanceInfo instanceInfo,
    Lease<InstanceInfo> existingLease,
    boolean isReplication) {
    // ReplicationInstance is DOWN or STARTING - believe that, but when the instance says UP, question that
    // The client instance sends STARTING or DOWN (because of heartbeat failures), then we accept what
    // the client says. The same is the case with replica as well.
    // The OUT_OF_SERVICE from the client or replica needs to be confirmed as well since the service may be
    // currently in SERVICE
    if ((!InstanceInfo.InstanceStatus.UP.equals(instanceInfo.getStatus()))
    && (!InstanceInfo.InstanceStatus.OUT_OF_SERVICE.equals(instanceInfo.getStatus()))) {
    logger.debug("Trusting the instance status {} from replica or instance for instance {}",
    instanceInfo.getStatus(), instanceInfo.getId());
    return StatusOverrideResult.matchingStatus(instanceInfo.getStatus());
    }
    return StatusOverrideResult.NO_MATCH;
    }
    • 注意,使用的是 instanceInfo

    4.1.3 OverrideExistsRule

    com.netflix.eureka.registry.rule.OverrideExistsRule ,匹配應用實例覆蓋狀態映射( statusOverrides ) 。實現 #apply(...) 代碼如下:


    public class OverrideExistsRule implements InstanceStatusOverrideRule {
    private Map<String, InstanceInfo.InstanceStatus> statusOverrides;
    @Override
    public StatusOverrideResult apply(InstanceInfo instanceInfo, Lease<InstanceInfo> existingLease, boolean isReplication) {
    InstanceInfo.InstanceStatus overridden = statusOverrides.get(instanceInfo.getId());
    // If there are instance specific overrides, then they win - otherwise the ASG status
    if (overridden != null) {
    logger.debug("The instance specific override for instance {} and the value is {}",
    instanceInfo.getId(), overridden.name());
    return StatusOverrideResult.matchingStatus(overridden);
    }
    return StatusOverrideResult.NO_MATCH;
    }
    }
    • 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(...) 代碼如下:


    public StatusOverrideResult apply(InstanceInfo instanceInfo,
    Lease<InstanceInfo> existingLease,
    boolean isReplication) {
    // This is for backward compatibility until all applications have ASG
    // names, otherwise while starting up
    // the client status may override status replicated from other servers
    if (!isReplication) { // 非 Eureka-Server 請求
    InstanceInfo.InstanceStatus existingStatus = null;
    if (existingLease != null) {
    existingStatus = existingLease.getHolder().getStatus();
    }
    // Allow server to have its way when the status is UP or OUT_OF_SERVICE
    if ((existingStatus != null)
    && (InstanceInfo.InstanceStatus.OUT_OF_SERVICE.equals(existingStatus)
    || InstanceInfo.InstanceStatus.UP.equals(existingStatus))) {
    logger.debug("There is already an existing lease with status {} for instance {}",
    existingLease.getHolder().getStatus().name(),
    existingLease.getHolder().getId());
    return StatusOverrideResult.matchingStatus(existingLease.getHolder().getStatus());
    }
    }
    return StatusOverrideResult.NO_MATCH;
    }
    • 注意,使用的是 existingLease ,並且非 Eureka-Server 請求。

    4.1.5 AlwaysMatchInstanceStatusRule

    com.netflix.eureka.registry.rule.AlwaysMatchInstanceStatusRule ,總是匹配關注狀態的實例對象( instanceInfo )的狀態。實現 #apply(...) 代碼如下:


    @Override
    public StatusOverrideResult apply(InstanceInfo instanceInfo,
    Lease<InstanceInfo> existingLease,
    boolean isReplication) {
    logger.debug("Returning the default instance status {} for instance {}", instanceInfo.getStatus(),
    instanceInfo.getId());
    return StatusOverrideResult.matchingStatus(instanceInfo.getStatus());
    }
    • 注意,使用的是 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 ,

    4.2 註冊場景

    // AbstractInstanceRegistry.java
    1: public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    2: try {
    3: // ((省略代碼) )獲取鎖
    4: Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
    5: // (省略代碼) 增加 註冊次數 到 監控
    6: // (省略代碼) 獲得 應用實例信息 對應的 租約
    7: Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
    8: // Retain the last dirty timestamp without overwriting it, if there is already a lease
    9: if (existingLease != null && (existingLease.getHolder() != null)) { // (省略代碼) 已存在時,使用數據不一致的時間大的應用註冊信息爲有效的
    10: } else {
    11: // The lease does not exist and hence it is a new registration
    12: // (省略代碼) 【自我保護機制】增加 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin
    13: }
    14: // 創建 租約
    15: Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
    16: if (existingLease != null) { // 若租約已存在,設置 租約的開始服務的時間戳
    17: lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
    18: }
    19: // 添加到 租約映射
    20: gMap.put(registrant.getId(), lease);
    21: // (省略代碼) 添加到 最近註冊的調試隊列
    22: // (省略代碼) 添加到 應用實例覆蓋狀態映射(Eureka-Server 初始化使用)
    23: // 設置 應用實例覆蓋狀態
    24: InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
    25: if (overriddenStatusFromMap != null) {
    26: logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
    27: registrant.setOverriddenStatus(overriddenStatusFromMap);
    28: }
    29:
    30: // 獲得 應用實例狀態
    31: // Set the status based on the overridden status rules
    32: InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
    33: // 設置 應用實例狀態
    34: registrant.setStatusWithoutDirty(overriddenInstanceStatus);
    35:
    36: // (省略代碼) 設置 租約的開始服務的時間戳(只有第一次有效)
    37: // (省略代碼) 設置 應用實例信息的操作類型 爲 添加
    38: // (省略代碼) 添加到 最近租約變更記錄隊列
    39: // (省略代碼) 設置 租約的最後更新時間戳
    40: // (省略代碼) 設置 響應緩存 過期
    41: } finally {
    42: // (省略代碼) 釋放鎖
    43: }
    44: }
    • 第 7 行 :獲得已存在的租約( existingLease ) 。
    • 第 15 行 :創建新的租約( lease )。
    • 第 24 至 28 行 :設置應用實例的覆蓋狀態( overridestatus ),避免註冊應用實例後,丟失覆蓋狀態。
    • 第 30 至 32 行 :獲得應用實例最終狀態。注意下,不考慮第 9 行代碼的情況,registrantexistingLease 的應用實例不是同一個對象。
    • 第 33 只 34 行 :設置應用實例的狀態。

    4.3 續租場景

    // AbstractInstanceRegistry.java
    1: public boolean renew(String appName, String id, boolean isReplication) {
    2: // (省略代碼)增加 續租次數 到 監控
    3: // 獲得 租約
    4: Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
    5: Lease<InstanceInfo> leaseToRenew = null;
    6: if (gMap != null) {
    7: leaseToRenew = gMap.get(id);
    8: }
    9: // (省略代碼)租約不存在
    10: if (leaseToRenew == null) {
    11: return false;
    12: } else {
    13: InstanceInfo instanceInfo = leaseToRenew.getHolder();
    14: if (instanceInfo != null) {
    15: // 獲得 應用實例狀態
    16: InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
    17: instanceInfo, leaseToRenew, isReplication);
    18: // 應用實例狀態未知,無法續約
    19: if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
    20: logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"
    21: + "; re-register required", instanceInfo.getId());
    22: RENEW_NOT_FOUND.increment(isReplication);
    23: return false;
    24: }
    25: // 設置 應用實例狀態
    26: if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
    27: Object[] args = {
    28: instanceInfo.getStatus().name(),
    29: instanceInfo.getOverriddenStatus().name(),
    30: instanceInfo.getId()
    31: };
    32: logger.info(
    33: "The instance status {} is different from overridden instance status {} for instance {}. "
    34: + "Hence setting the status to overridden status", args);
    35: instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);
    36: }
    37: }
    38: // (省略代碼)新增 續租每分鐘次數
    39: // (省略代碼)設置 租約最後更新時間(續租)
    40: return true;
    41: }
    42: }

    • 第 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
    protected boolean internalCancel(String appName, String id, boolean isReplication) {
    // ... 省略無關代碼
    // 移除 應用實例覆蓋狀態映射
    InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
    if (instanceStatus != null) {
    logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
    }
    }

    4.5 過期場景

    「4.4 下線場景」 相同。


    5. 客戶端調用接口

    對應用實例覆蓋狀態的變更和刪除接口調用,點擊如下方法查看,非常易懂,本文就不囉嗦了:

發佈了22 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章