热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

1.概述本文主要分享应用实例的覆盖状态属性。这里要注意下,不是应用实例的状态(status),而是覆盖状态(overridestatus)。代码如下
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微服务架构实战》
  • 两书齐买,京东包邮。


接口 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: @Override2: 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> gMap = registry.get(appName);
12: Lease 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 overriddenInstanceStatusMap = CacheBuilder.newBuilder().initialCapacity(500).expireAfterAccess(1, TimeUnit.HOURS).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, null, null, isReplication);return true;}return false;
}

    • 调用父类 AbstractInstanceRegistry#deleteStatusOverride(...) 方法,删除应用实例覆盖状态。

3.1 删除应用实例覆盖状态

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

1: @Override2: 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> gMap = registry.get(appName);
13: Lease 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 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 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 &#61; rules;this.defaultRule &#61; new AlwaysMatchInstanceStatusRule();// Let&#39;s build up and "cache" the rule name to be used by toString();List ruleNames &#61; new ArrayList<>(rules.length&#43;1);for (int i &#61; 0; i existingLease,boolean isReplication) {// 使用复合规则&#xff0c;顺序匹配&#xff0c;直到匹配成功for (int i &#61; 0; i }

  • rules 属性&#xff0c;复合规则集合。在 PeerAwareInstanceRegistryImpl 里&#xff0c;我们可以看到该属性为 [ DownOrStartingRule , OverrideExistsRule , LeaseExistsRule ] 。
  • defaultRule 属性&#xff0c;默认规则&#xff0c;值为 AlwaysMatchInstanceStatusRule 。
  • #apply() 方法&#xff0c;优先使用复合规则( rules )&#xff0c;顺序匹配&#xff0c;直到匹配成功 。当未匹配成功&#xff0c;使用默认规则( defaultRule ) 。

4.1.2 DownOrStartingRule

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

&#64;Override
public StatusOverrideResult apply(InstanceInfo instanceInfo,Lease 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 SERVICEif ((!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;
}

  • 注意&#xff0c;使用的是 instanceInfo 。

4.1.3 OverrideExistsRule

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

public class OverrideExistsRule implements InstanceStatusOverrideRule {private Map statusOverrides;&#64;Overridepublic StatusOverrideResult apply(InstanceInfo instanceInfo, Lease existingLease, boolean isReplication) {InstanceInfo.InstanceStatus overridden &#61; statusOverrides.get(instanceInfo.getId());// If there are instance specific overrides, then they win - otherwise the ASG statusif (overridden !&#61; 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 属性&#xff0c;应用实例覆盖状态映射。在 PeerAwareInstanceRegistryImpl 里&#xff0c;使用 AbstractInstanceRegistry.overriddenInstanceStatusMap 属性赋值。
  • 上文我们提到 AbstractInstanceRegistry.overriddenInstanceStatusMap 每次访问刷新有效期&#xff0c;如果调用到 OverrideExistsRule &#xff0c;则会不断刷新。从 DownOrStartingRule 看到&#xff0c;instanceInfo 处于 InstanceInfo.InstanceStatus.DOWN 或者 InstanceInfo.InstanceStatus.STARTING 才不会继续调用 OverrideExistsRule 匹配&#xff0c;AbstractInstanceRegistry.overriddenInstanceStatusMap 才有可能过期。

4.1.4 LeaseExistsRule

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

public StatusOverrideResult apply(InstanceInfo instanceInfo,Lease 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 serversif (!isReplication) { // 非 Eureka-Server 请求InstanceInfo.InstanceStatus existingStatus &#61; null;if (existingLease !&#61; null) {existingStatus &#61; existingLease.getHolder().getStatus();}// Allow server to have its way when the status is UP or OUT_OF_SERVICEif ((existingStatus !&#61; 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;
}

  • 注意&#xff0c;使用的是 existingLease &#xff0c;并且非 Eureka-Server 请求。

4.1.5 AlwaysMatchInstanceStatusRule

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

&#64;Override
public StatusOverrideResult apply(InstanceInfo instanceInfo,Lease existingLease,boolean isReplication) {logger.debug("Returning the default instance status {} for instance {}", instanceInfo.getStatus(),instanceInfo.getId());return StatusOverrideResult.matchingStatus(instanceInfo.getStatus());
}

  • 注意&#xff0c;使用的是 instanceInfo 。

4.1.6 总结

我们将 PeerAwareInstanceRegistryImpl 的应用实例覆盖状态规则梳理如下&#xff1a;

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

  • 在下文中&#xff0c;你会看到&#xff0c;#getOverriddenInstanceStatus() 方法会在注册续租使用到&#xff0c;方法参数 instanceInfo 情况如下&#xff1a;
    • 注册时 &#xff1a;请求参数 instanceInfo &#xff0c;和 existingLease 的应用实例属性不相等( 如果考虑 Eureka-Server 的 LastDirtyTimestamp 更大的情况&#xff0c;则类似 续租时的情况 ) 。
    • 续租时 &#xff1a;使用 Eureka-Server 的 existingLease 的应用实例&#xff0c;两者相等。
    • 总的来说&#xff0c;可以将 instanceInfo 理解成请求方的状态
  • DownOrStartingRule &#xff0c;

4.2 注册场景

// AbstractInstanceRegistry.java1: public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {2: try {3: // &#xff08;(省略代码) &#xff09;获取锁4: Map> gMap &#61; registry.get(registrant.getAppName());5: // (省略代码) 增加 注册次数 到 监控6: // (省略代码) 获得 应用实例信息 对应的 租约7: Lease existingLease &#61; gMap.get(registrant.getId());8: // Retain the last dirty timestamp without overwriting it, if there is already a lease9: if (existingLease !&#61; null && (existingLease.getHolder() !&#61; null)) { // (省略代码) 已存在时&#xff0c;使用数据不一致的时间大的应用注册信息为有效的10: } else {11: // The lease does not exist and hence it is a new registration12: // (省略代码) 【自我保护机制】增加 &#96;numberOfRenewsPerMinThreshold&#96; 、&#96;expectedNumberOfRenewsPerMin&#96;13: }14: // 创建 租约15: Lease lease &#61; new Lease(registrant, leaseDuration);16: if (existingLease !&#61; null) { // 若租约已存在&#xff0c;设置 租约的开始服务的时间戳17: lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());18: }19: // 添加到 租约映射20: gMap.put(registrant.getId(), lease);21: // (省略代码) 添加到 最近注册的调试队列22: // (省略代码) 添加到 应用实例覆盖状态映射&#xff08;Eureka-Server 初始化使用&#xff09;23: // 设置 应用实例覆盖状态24: InstanceStatus overriddenStatusFromMap &#61; overriddenInstanceStatusMap.get(registrant.getId());25: if (overriddenStatusFromMap !&#61; 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 rules32: InstanceStatus overriddenInstanceStatus &#61; getOverriddenInstanceStatus(registrant, existingLease, isReplication);33: // 设置 应用实例状态34: registrant.setStatusWithoutDirty(overriddenInstanceStatus);35: 36: // (省略代码) 设置 租约的开始服务的时间戳&#xff08;只有第一次有效&#xff09;37: // (省略代码) 设置 应用实例信息的操作类型 为 添加38: // (省略代码) 添加到 最近租约变更记录队列39: // (省略代码) 设置 租约的最后更新时间戳40: // (省略代码) 设置 响应缓存 过期41: } finally {42: // (省略代码) 释放锁43: }44: }

  • 第 7 行 &#xff1a;获得已存在的租约( existingLease ) 。
  • 第 15 行 &#xff1a;创建新的租约( lease )。
  • 第 24 至 28 行 &#xff1a;设置应用实例的覆盖状态( overridestatus )&#xff0c;避免注册应用实例后&#xff0c;丢失覆盖状态。
  • 第 30 至 32 行 &#xff1a;获得应用实例最终状态。注意下&#xff0c;不考虑第 9 行代码的情况&#xff0c;registrant 和 existingLease 的应用实例不是同一个对象。
  • 第 33 只 34 行 &#xff1a;设置应用实例的状态。

4.3 续租场景

// AbstractInstanceRegistry.java1: public boolean renew(String appName, String id, boolean isReplication) {2: // &#xff08;省略代码&#xff09;增加 续租次数 到 监控3: // 获得 租约4: Map> gMap &#61; registry.get(appName);5: Lease leaseToRenew &#61; null;6: if (gMap !&#61; null) {7: leaseToRenew &#61; gMap.get(id);8: }9: // &#xff08;省略代码&#xff09;租约不存在10: if (leaseToRenew &#61;&#61; null) {11: return false;12: } else {13: InstanceInfo instanceInfo &#61; leaseToRenew.getHolder();14: if (instanceInfo !&#61; null) {15: // 获得 应用实例状态16: InstanceStatus overriddenInstanceStatus &#61; this.getOverriddenInstanceStatus(17: instanceInfo, leaseToRenew, isReplication);18: // 应用实例状态未知&#xff0c;无法续约19: if (overriddenInstanceStatus &#61;&#61; InstanceStatus.UNKNOWN) {20: logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"21: &#43; "; 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 &#61; {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: &#43; "Hence setting the status to overridden status", args);35: instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);36: }37: }38: // &#xff08;省略代码&#xff09;新增 续租每分钟次数39: // &#xff08;省略代码&#xff09;设置 租约最后更新时间&#xff08;续租&#xff09;40: return true;41: }42: }

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

4.4 下线场景

// AbstractInstanceRegistry.java
protected boolean internalCancel(String appName, String id, boolean isReplication) {// ... 省略无关代码// 移除 应用实例覆盖状态映射InstanceStatus instanceStatus &#61; overriddenInstanceStatusMap.remove(id);if (instanceStatus !&#61; null) {logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());}}

4.5 过期场景

同 「4.4 下线场景」 相同。

5. 客户端调用接口

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

  • AbstractJerseyEurekaHttpClient#statusUpdate(...)
  • AbstractJerseyEurekaHttpClient#deleteStatusOverride(...)

推荐阅读
author-avatar
JIE9118_755
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有