⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Eureka/instance-registry-override-status/ 「芋道源码」欢迎转载,保留摘要,谢谢!

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


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  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 书籍


接口 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: // (省略代码) 【自我保护机制】增加 `numberOfRenewsPerMinThreshold` 、`expectedNumberOfRenewsPerMin`
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. 客户端调用接口

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

666. 彩蛋

知识星球

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

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

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

文章目录
  1. 1. 1. 概述
  2. 2. 2. 应用实例覆盖状态变更接口
    1. 2.1. 2.1 更新应用实例覆盖状态
  3. 3. 3. 应用实例覆盖状态删除接口
    1. 3.1. 3.1 删除应用实例覆盖状态
  4. 4. 4. 应用实例覆盖状态映射
    1. 4.1. 4.1 应用实例状态覆盖规则
      1. 4.1.1. 4.1.1 FirstMatchWinsCompositeRule
      2. 4.1.2. 4.1.2 DownOrStartingRule
      3. 4.1.3. 4.1.3 OverrideExistsRule
      4. 4.1.4. 4.1.4 LeaseExistsRule
      5. 4.1.5. 4.1.5 AlwaysMatchInstanceStatusRule
      6. 4.1.6. 4.1.6 总结
    2. 4.2. 4.2 注册场景
    3. 4.3. 4.3 续租场景
    4. 4.4. 4.4 下线场景
    5. 4.5. 4.5 过期场景
  5. 5. 5. 客户端调用接口
  6. 6. 666. 彩蛋