系列文章目录
二.SpringCloud源码剖析-Eureka Client 初始化过程
五.SpringCloud源码剖析-Eureka Client服务续约
六.SpringCloud源码剖析-Eureka Client取消注册
七.SpringCloud源码剖析-Eureka Server的自动配置
八.SpringCloud源码剖析-Eureka Server初始化流程
九.SpringCloud源码剖析-Eureka Server服务注册流程
十.SpringCloud源码剖析-Eureka Server服务续约
十一.SpringCloud源码剖析-Eureka Server服务注册表拉取
十二.SpringCloud源码剖析-Eureka Server服务剔除
Eureka Server 服务下线
在《Eureka Client取消注册》中我们有分析到,当Eureka Client 客户端当服务关闭,触发客户端服务下线方法,客户端执行一系列下线逻辑后会向Eureka Server服务端发送服务下线请求,服务端处理下线的请求是在com.netflix.eureka.resources.InstanceResource#cancelLease方法中
/**
处理此特定实例的租赁取消
* Handles cancellation of leases for this particular instance.
*
* @param isReplication
* a header parameter containing information whether this is
* replicated from other nodes.
* @return response indicating whether the operation was a success or
* failure.
*/
@DELETE
public Response cancelLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
try {
//调用 InstanceRegistry的 cancel 方法下线服务
boolean isSuccess = registry.cancel(app.getName(), id,
"true".equals(isReplication));
if (isSuccess) {
logger.debug("Found (Cancel): {} - {}", app.getName(), id);
return Response.ok().build();
} else {
logger.info("Not Found (Cancel): {} - {}", app.getName(), id);
return Response.status(Status.NOT_FOUND).build();
}
} catch (Throwable e) {
logger.error("Error (cancel): {} - {}", app.getName(), id, e);
return Response.serverError().build();
}
}
InstanceRegistry的 cancel 方法
public class InstanceRegistry extends PeerAwareInstanceRegistryImpl
implements ApplicationContextAware {
@Override
public boolean cancel(String appName, String serverId, boolean isReplication) {
//抛出EurekaInstanceCanceledEvent取消事件
handleCancelation(appName, serverId, isReplication);
//调用PeerAwareInstanceRegistryImpl#cancel
return super.cancel(appName, serverId, isReplication);
}
//抛出EurekaInstanceCanceledEvent取消事件
private void handleCancelation(String appName, String id, boolean isReplication) {
log("cancel " + appName + ", serverId " + id + ", isReplication " + isReplication);
publishEvent(new EurekaInstanceCanceledEvent(this, appName, id, isReplication));
}
这里比较简单,抛出EurekaInstanceCanceledEvent时间后调用super(PeerAwareInstanceRegistryImpl)的下线方法
/*
* (non-Javadoc)
*
* @see com.netflix.eureka.registry.InstanceRegistry#cancel(java.lang.String,
* java.lang.String, long, boolean)
*/
@Override
public boolean cancel(final String appName, final String id,
final boolean isReplication) {
//调用super.cancel下线方法:com.netflix.eureka.registry.AbstractInstanceRegistry#cancel
if (super.cancel(appName, id, isReplication)) {
//复制到其他的Eureka节点
replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
synchronized (lock) {
if (this.expectedNumberOfRenewsPerMin > 0) {
//如果每分钟的预期续订次数大于0
//由于客户想取消该阈值,因此降低阈值(1次 30秒,2次 1分钟)
// Since the client wants to cancel it, reduce the threshold (1 for 30 seconds, 2 for a minute)
//预期续订次数 = 预期续订次数 -2 (2次 1分钟 , 1次 30)
this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
//续订次数阈值 = 预期续订次数 * 0.85
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}
}
return true;
}
return false;
}
这里继续调用super.cancel(AbstractInstanceRegistry#cancel),完成下线后重新计算了续约阈值,继续看AbstractInstanceRegistry#cancel方法
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
/**
//取消实例注册
//这通常是由客户端调用时关闭通知服务器从流量中除去实例
* Cancels the registration of an instance.
*
* <p>
* This is normally invoked by a client when it shuts down informing the
* server to remove the instance from traffic.
* </p>
*
* @param appName the application name of the application.
* @param id the unique identifier of the instance.
* @param isReplication true if this is a replication event from other nodes, false
* otherwise.
* @return true if the instance was removed from the {@link AbstractInstanceRegistry} successfully, false otherwise.
*/
@Override
public boolean cancel(String appName, String id, boolean isReplication) {
return internalCancel(appName, id, isReplication);
}
/**
内部取消服务注册
* {@link #cancel(String, String, boolean)} method is overridden by {@link PeerAwareInstanceRegistry}, so each
* cancel request is replicated to the peers. This is however not desired for expires which would be counted
* in the remote peers as valid cancellations, so self preservation mode would not kick-in.
*/
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) {
//从服务注册的map中移除掉当前服务
leaseToCancel = gMap.remove(id);
}
//添加到最近取消队列
synchronized (recentCanceledQueue) {
recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
}
//overriddenInstanceStatusMap 服务状态map中移除当前服务
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);
return false;
} else {
//调用Lease.cancel方法
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();
}
//使缓存无效,调用responseCache.invalidate让服务在缓存中失效
invalidateCache(appName, vip, svip);
logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
return true;
}
} finally {
read.unlock();
}
}
AbstractInstanceRegistry的cancel方法中调用了internalCancel去取消服务的注册,其实在之间分析服务剔除的时候也嗲用这个方法,方法的逻辑大致是:
- 1.从registry中移除服务,
- 2.从overriddenInstanceStatusMap状态map中移除服务状态
- 3.添加到最近取消队列
- 4.调用Lease.cancel方法,将租约对象中的逐出时间修改为当前时间
- 5.修改服务的InstanceInfo的状态为DELETE
- 6.添加到最近修改队列
- 7.更新服务最后修改时间
- 8.使ReponseCache缓存无效
总结
我们发现,Eureka Server服务剔除和服务下线的流程后半截很相似,一个是通过定时任务来实现服务端过期的服务的剔除,一个是客户端发送请求到服务端剔除指定的服务,都是走服务下线逻辑