Eureka源码分析--服务下线(7)
前言
有了client对server的主动心跳续约说明“我还活着”,那么当client主动下线的时候需不需要对server说一声呢?当然要了,毕竟client是一个good boy。
client端的流程
在DiscoveryClient类中有一个标志性单词,shutdown方法,但是这个方法需要自己手动掉才会运行
public synchronized void shutdown() {
if (isShutdown.compareAndSet(false, true)) {
logger.info("Shutting down DiscoveryClient ...");
if (statusChangeListener != null && applicationInfoManager != null) {
applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
}
//停止各种线程的调用
cancelScheduledTasks();
// If APPINFO was registered
if (applicationInfoManager != null
&& clientConfig.shouldRegisterWithEureka()
&& clientConfig.shouldUnregisterOnShutdown()) {
//设置实例状态
applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
//向server端发送通知
unregister();
}
if (eurekaTransport != null) {
eurekaTransport.shutdown();
}
heartbeatStalenessMonitor.shutdown();
registryStalenessMonitor.shutdown();
Monitors.unregisterObject(this);
logger.info("Completed shut down of DiscoveryClient");
}
}
void unregister() {
// It can be null if shouldRegisterWithEureka == false
if(eurekaTransport != null && eurekaTransport.registrationClient != null) {
try {
logger.info("Unregistering ...");
EurekaHttpResponse<Void> httpResponse = eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
logger.info(PREFIX + "{} - deregister status: {}", appPathIdentifier, httpResponse.getStatusCode());
} catch (Exception e) {
logger.error(PREFIX + "{} - de-registration failed{}", appPathIdentifier, e.getMessage(), e);
}
}
}
整个client端的停机并不复杂,就是先把启动时创建的各种线程都停掉,在向server发送一个下线通知,client端就结束了
server接收下线通知
client最后会请求到eureka-core下InstanceResource中的cancelLease()方法
该方法会根据服务名称和实例id进行一系列操作,最后会调用到AbstractInstanceRegistery.internalCancel()方法
这个方法在服务server进行服务剔除的时候已经分析过,其实就是一个方法被公用了
protected boolean internalCancel(String appName, String id, boolean isReplication) {
//加读锁
read.lock();
try {
CANCEL.increment(isReplication);
//获得要清除的服务实例
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToCancel = null;
if (gMap != null) {
leaseToCancel = gMap.remove(id);
}
//添加进入清除队列
recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
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 {
//设置失效时间
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);
}
} finally {
read.unlock();
}
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// Since the client wants to cancel it, reduce the number of clients to send renews.
//更新每分钟期望收到心跳次数的阈值
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
updateRenewsPerMinThreshold();
}
}
return true;
}
总结
服务下线在client做的事情相对较简单,而server对应的处理方法也和定时剔除失效实例的方法一致,这也就体现了代码的复用性,也更好的展示了一段好的逻辑应该是怎么样的。