0. 前言
- springboot版本:2.1.9.RELEASE
- springcloud版本:Greenwich.SR4
1. 处理客户端下架请求
服务端处理处理客户端下架请求在 InstanceResource 类的 cancelLease() 方法
// InstanceResource.class
public Response cancelLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
// isReplication:是否是集群节点间的同步复制
try {
// 2 处理客户端下架
boolean isSuccess = registry.cancel(app.getName(), id,
"true".equals(isReplication));
if (isSuccess) {
logger.debug("Found (Cancel): {} - {}", app.getName(), id);
// 处理成功返回200
return Response.ok().build();
} else {
logger.info("Not Found (Cancel): {} - {}", app.getName(), id);
// 处理失败返回404
return Response.status(Status.NOT_FOUND).build();
}
} catch (Throwable e) {
logger.error("Error (cancel): {} - {}", app.getName(), id, e);
// 处理异常返回500
return Response.serverError().build();
}
}
2. registry.cancel()
// PeerAwareInstanceRegistryImpl.class
public boolean cancel(final String appName, final String id,
final boolean isReplication) {
// 3 调用父类处理下架方法
if (super.cancel(appName, id, isReplication)) {
// 2.1 同步复制给集群节点
replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// Since the client wants to cancel it, reduce the number of clients to send renews
// 处理成功后,预期收到心跳续租实例数-1
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
// 2.2 更新 预期每分钟收到心跳续租请求数
updateRenewsPerMinThreshold();
}
}
return true;
}
return false;
}
2.1 replicateToPeers()
《EurekaServer-同步注册表机制》中已讲解
2.2 updateRenewsPerMinThreshold()
《EurekaServer-同步注册表机制》中已讲解
3. super.cancel()
// AbstractInstanceRegistry.class
public boolean cancel(String appName, String id, boolean isReplication) {
return internalCancel(appName, id, isReplication);
}
protected boolean internalCancel(String appName, String id, boolean isReplication) {
try {
// 开启读锁
read.lock();
CANCEL.increment(isReplication);
// 获取服务租约信息
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToCancel = null;
if (gMap != null) {
// 如果服务租约信息存在,则删除其中的实例租约信息
leaseToCancel = gMap.remove(id);
}
synchronized (recentCanceledQueue) {
// 把实例下架时间和实例名的映射关系加入最近下架队列
recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
}
// 获取覆盖状态,并从 overriddenInstanceStatusMap 中删除
InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
if (instanceStatus != null) {
logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
}
if (leaseToCancel == null) {
CANCEL_NOT_FOUND.increment(isReplication);
logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
// 如果获取不到实例租约信息,则返回 false
return false;
} else {
// 实例租约信息中设置从服务端下架的时间
leaseToCancel.cancel();
InstanceInfo instanceInfo = leaseToCancel.getHolder();
String vip = null;
String svip = null;
if (instanceInfo != null) {
// 设置行为类型为删除
instanceInfo.setActionType(ActionType.DELETED);
// 实例变更信息放入最新变更队列
recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
// 设置本地相应实例信息的最新修改时间戳(非脏时间戳)
instanceInfo.setLastUpdatedTimestamp();
// 获取实例的虚拟互联网协议地址,如果未指定则默认为主机名
vip = instanceInfo.getVIPAddress();
// 获取实例的安全虚拟互联网协议地址,如果未指定则默认为主机名
svip = instanceInfo.getSecureVipAddress();
}
// 让相应缓存失效
invalidateCache(appName, vip, svip);
logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
return true;
}
} finally {
// 关闭读锁
read.unlock();
}
}
4. 总结
- 当服务端收到客户端下架请求时,会先从本地获取相应服务实例的租约信息并删除
- 然后把实例下架时间和服务实例名的映射信息添加到最近下架队列,删除 overriddenInstanceStatusMap 中相应覆盖状态
- 接着在实例租约信息中记录下架时间,转换成实例变更信息添加到最新变更队列,设置相应实例信息的最新修改时间戳(非脏时间戳)
- 最后,让相应缓存失效,同步复制给集群节点,本地预期收到心跳续租实例数-1,更新预期每分钟收到心跳续租的请求数