下面我们来分析一下eureka的下线。应用实例关闭时,Eureka-Client 向 Eureka-Server 发起下线应用实例。需要满足如下条件才可发起:
配置 eureka.registration.enabled = true ,应用实例开启注册开关。默认为 false 。
配置 eureka.shouldUnregisterOnShutdown = true ,应用实例开启关闭时下线开关。默认为 true 。
看一下具体的实现
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() // eureka.registration.enabled = true
&& clientConfig.shouldUnregisterOnShutdown()) { // eureka.shouldUnregisterOnShutdown = true
applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
unregister();
}
if (eurekaTransport != null) {
eurekaTransport.shutdown();
}
heartbeatStalenessMonitor.shutdown();
registryStalenessMonitor.shutdown();
logger.info("Completed shut down of DiscoveryClient");
}
}
调用unregister方法
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 + appPathIdentifier + " - deregister status: " + httpResponse.getStatusCode());
} catch (Exception e) {
logger.error(PREFIX + appPathIdentifier + " - de-registration failed" + e.getMessage(), e);
}
}
}
调用 AbstractJerseyEurekaHttpClient的cancel方法,DELETE 请求 Eureka-Server 的 apps/APP_NAME/${INSTANCE_INFO_ID} 接口,实现应用实例的下线。
InstanceResource,处理单个应用实例信息的请求操作的 Resource ( Controller )。
下线应用实例信息的请求,映射 InstanceResource的cancelLease方法
@DELETE
public Response cancelLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
// 下线
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();
}
}
调用 PeerAwareInstanceRegistryImpl的cancel方法,下线应用实例。
public boolean cancel(final String appName, final String id,
final boolean isReplication) {
if (super.cancel(appName, id, isReplication)) { // 下线
// Eureka-Server 复制
replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
// 【自我保护机制】增加 `numberOfRenewsPerMinThreshold` 、`expectedNumberOfRenewsPerMin`
synchronized (lock) {
if (this.expectedNumberOfRenewsPerMin > 0) {
// Since the client wants to cancel it, reduce the threshold (1 for 30 seconds, 2 for a minute)
this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}
}
return true;
}
return false;
}
调用AbstractInstanceRegistry的cancel方法下线应用实例。
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 + ")"));
}
// 移除 应用实例覆盖状态映射
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);
return true; // 成功
}
} finally {
// 释放锁
read.unlock();
}
}
eureka的下线流程就完成了。
InstanceResource的分析就到这里了。
网友评论