内容简介:注册中心 Eureka 源码解析 —— 应用实例注册发现(八)之覆盖状态
摘要: 原创出处 http://www.iocoder.cn/Eureka/instance-registry-override-status/ 「芋道源码」欢迎转载,保留摘要,谢谢!
本文主要基于 Eureka 1.8.X 版本
关注**微信公众号:【芋道源码】**有福利:
- RocketMQ / MyCAT / Sharding-JDBC 所有 源码分析文章列表
- RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
- 您对于源码的疑问每条留言 都 将得到 认真 回复。 甚至不知道如何读源码也可以请教噢 。
- 新的 源码解析文章 实时 收到通知。 每周更新一篇左右 。
- 认真的 源码交流微信群。
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: @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#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: @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
。
实现类关系如下:
- 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()
方法会在 注册 和 续租 使用到。结合上图,我们在和也会详细解析。
- 在下文中,你会看到,
#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 行代码的情况,
registrant
和existingLease
的应用实例不是同一个对象。 - 第 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. 客户端调用接口
对应用实例覆盖状态的变更和删除接口调用,点击如下方法查看,非常易懂,本文就不啰嗦了:
-
AbstractJerseyEurekaHttpClient#statusUpdate(...)
-
AbstractJerseyEurekaHttpClient#deleteStatusOverride(...)
666. 彩蛋
猜测覆盖状态的花费了较长时间,梳理应用实例覆盖规则耗费大量脑细胞。
下一篇,让我鸡鸡动动的,Eureka-Server 集群同步走起!
胖友,分享我的公众号( 芋道源码 ) 给你的胖友可好?
以上所述就是小编给大家介绍的《注册中心 Eureka 源码解析 —— 应用实例注册发现(八)之覆盖状态》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- vue 源码学习 - 实例挂载
- 深入剖析Vue源码 - 实例挂载,编译流程
- Vue 源码解析 - 实例化 Vue 前(一)
- Vue 源码解析 - 实例化 Vue 前(二)
- 内核通信之 Netlink 源码分析和实例分析
- 内核通信之 Netlink 源码分析和实例分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。