热门标签 | 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(...)

推荐阅读
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • 本文讨论了如何在codeigniter中识别来自angularjs的请求,并提供了两种方法的代码示例。作者尝试了$this->input->is_ajax_request()和自定义函数is_ajax(),但都没有成功。最后,作者展示了一个ajax请求的示例代码。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • Vagrant虚拟化工具的安装和使用教程
    本文介绍了Vagrant虚拟化工具的安装和使用教程。首先介绍了安装virtualBox和Vagrant的步骤。然后详细说明了Vagrant的安装和使用方法,包括如何检查安装是否成功。最后介绍了下载虚拟机镜像的步骤,以及Vagrant镜像网站的相关信息。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • Sleuth+zipkin链路追踪SpringCloud微服务的解决方案
    在庞大的微服务群中,随着业务扩展,微服务个数增多,系统调用链路复杂化。Sleuth+zipkin是解决SpringCloud微服务定位和追踪的方案。通过TraceId将不同服务调用的日志串联起来,实现请求链路跟踪。通过Feign调用和Request传递TraceId,将整个调用链路的服务日志归组合并,提供定位和追踪的功能。 ... [详细]
  • 本文介绍了DataTables插件的官方网站以及其基本特点和使用方法,包括分页处理、数据过滤、数据排序、数据类型检测、列宽度自动适应、CSS定制样式、隐藏列等功能。同时还介绍了其易用性、可扩展性和灵活性,以及国际化和动态创建表格的功能。此外,还提供了参数初始化和延迟加载的示例代码。 ... [详细]
  • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
    本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
  • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • centos安装Mysql的方法及步骤详解
    本文介绍了centos安装Mysql的两种方式:rpm方式和绿色方式安装,详细介绍了安装所需的软件包以及安装过程中的注意事项,包括检查是否安装成功的方法。通过本文,读者可以了解到在centos系统上如何正确安装Mysql。 ... [详细]
  • 在C#中,使用关键字abstract来定义抽象类和抽象方法。抽象类是一种不能被实例化的类,它只提供部分实现,但可以被其他类继承并创建实例。抽象类可以用于类、方法、属性、索引器和事件。在一个类声明中使用abstract表示该类倾向于作为其他类的基类成员被标识为抽象,或者被包含在一个抽象类中,必须由其派生类实现。本文介绍了C#中抽象类和抽象方法的基础知识,并提供了一个示例代码。 ... [详细]
  • ZABBIX 3.0 配置监控NGINX性能【OK】
    1.在agent端查看配置:nginx-V查看编辑时是否加入状态监控模块:--with-http_stub_status_module--with-http_gzip_stat ... [详细]
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社区 版权所有