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

点击上方“芋道源码”,选择“置顶公众号”

技术文章第一时间送达!

源码精品专栏

 

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

  • 1. 概述

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

    • 2.1 更新应用实例覆盖状态

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

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

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

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

    • 4.2 注册场景

    • 4.3 续租场景

    • 4.4 下线场景

    • 4.5 过期场景

  • 5. 客户端调用接口

  • 666. 彩蛋


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
  2public 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#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, nullnull, isReplication);
           return true;
       }
       return false;
    }
    • 调用父类 AbstractInstanceRegistry#deleteStatusOverride(...) 方法,删除应用实例覆盖状态。

3.1 删除应用实例覆盖状态

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

  1@Override
  2public 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
// ... 省略,微信长度限制
  • #apply(...) 方法参数 instanceInfo 代表的是关注状态的应用实例,和方法参数 existingLease 里的应用实例不一定是同一个,在 「4.1.6 总结」 详细解析。

  • com.netflix.eureka.registry.rule.StatusOverrideResult ,状态覆盖结果。当匹配成功,返回 matches = true ;否则,返回 matches = false 。

实现类关系如下

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

4.1.1 FirstMatchWinsCompositeRule

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

// ... 省略,微信长度限制
  • 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(...) 代码如下:

// ... 省略,微信长度限制
  • 注意,使用的是 instanceInfo 。

4.1.3 OverrideExistsRule

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

// ... 省略,微信长度限制
  • 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(...) 代码如下:

// ... 省略,微信长度限制
  • 注意,使用的是 existingLease ,并且非 Eureka-Server 请求。

4.1.5 AlwaysMatchInstanceStatusRule

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

// ... 省略,微信长度限制
  • 注意,使用的是 instanceInfo 。

4.1.6 总结

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

640
  • 应用实例状态是最重要的属性,没有之一,因而在最终实例状态的计算,以可信赖为主。

  • 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
// ... 省略,微信长度限制
  • 第 7 行 :获得已存在的租约( existingLease ) 。

  • 第 15 行 :创建新的租约( lease )。

  • 第 24 至 28 行 :设置应用实例的覆盖状态( overridestatus ),避免注册应用实例后,丢失覆盖状态。

  • 第 30 至 32 行 :获得应用实例最终状态。注意下,不考虑第 9 行代码的情况,registrant 和 existingLease 的应用实例不是同一个对象。

  • 第 33 只 34 行 :设置应用实例的状态。

4.3 续租场景

// AbstractInstanceRegistry.java
// ... 省略,微信长度限制
  • 第 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. 客户端调用接口

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

  • AbstractJerseyEurekaHttpClient#statusUpdate(...)

  • AbstractJerseyEurekaHttpClient#deleteStatusOverride(...)

666. 彩蛋

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

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

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

640?

知识星球



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

01. 调试环境搭建
02. 项目结构一览
03. API 配置(一)之应用
04. API 配置(二)之服务提供者
05. API 配置(三)之服务消费者
06. 属性配置
07. XML 配置
08. 核心流程一览

09. 拓展机制 SPI

10. 线程池

11. 服务暴露(一)之远程暴露(Injvm)

12. 服务暴露(二)之远程暴露(Dubbo)

...
一共 60 篇++


猜你喜欢

转载自blog.csdn.net/x5fnncxzq4/article/details/80788297