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

栏目: Java · 发布时间: 6年前

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

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

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

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

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

  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 里的应用实例不一定是同一个,在详细解析。
  • com.netflix.eureka.registry.rule.StatusOverrideResult ,状态覆盖结果。当匹配成功,返回 matches = true ;否则,返回 matches = false

实现类关系如下:

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

  • 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 的应用实例覆盖状态规则梳理如下:

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

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

同相同。

5. 客户端调用接口

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

666. 彩蛋

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

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

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


以上所述就是小编给大家介绍的《注册中心 Eureka 源码解析 —— 应用实例注册发现(八)之覆盖状态》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

刷新

刷新

[美] 萨提亚·纳德拉 / 陈召强、杨洋 / 中信出版集团 / 2018-1 / 58

《刷新:重新发现商业与未来》是微软CEO萨提亚•纳德拉首部作品。 互联网时代的霸主微软,曾经错失了一系列的创新机会。但是在智能时代,这家科技公司上演了一次出人意料的“大象跳舞”。2017年,微软的市值已经超过6000亿美元,在科技公司中仅次于苹果和谷歌,高于亚马逊和脸谱网。除了传统上微软一直占有竞争优势的软件领域,在云计算、人工智能等领域,微软也获得强大的竞争力。通过收购领英,微软还进入社交......一起来看看 《刷新》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具