Eureka服务下线源码解析
我们知道,在Eureka中,可以使用如下方法使Eureka主动下线,那么本篇文章就来分析一下子这个下线的流程
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);
unregister();
}
if (eurekaTransport != null) {
eurekaTransport.shutdown();
}
heartbeatStalenessMonitor.shutdown();
registryStalenessMonitor.shutdown();
logger.info("Completed shut down of DiscoveryClient");
}
}
主要做了这么几件事:
- 解除状态监听器
- 取消心跳、刷新线程
private void cancelScheduledTasks() {
if (instanceInfoReplicator != null) {
instanceInfoReplicator.stop();
}
if (heartbeatExecutor != null) {
heartbeatExecutor.shutdownNow();
}
if (cacheRefreshExecutor != null) {
cacheRefreshExecutor.shutdownNow();
}
if (scheduler != null) {
scheduler.shutdownNow();
}
}
- 向服务端发起下线通知
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);
}
}
}
@Override
public EurekaHttpResponse<Void> cancel(String appName, String id) {
String urlPath = "apps/" + appName + '/' + id;
ClientResponse response = null;
try {
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(resourceBuilder);
response = resourceBuilder.delete(ClientResponse.class);
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP DELETE {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
- 停止各个监听器
服务端接受下线消息
下线消息的处理在InstanceResource
类中
@DELETE
public Response cancelLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
try {
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();
}
}
public boolean cancel(final String appName, final String id,
final boolean isReplication) {
if (super.cancel(appName, id, isReplication)) {
//往集群同步下线信息
replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
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;
}
先看具体的下线逻辑,与租约过期清除的处理逻辑是一致的
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();
}
}
其中invalidateCache
则是删除当前服务中与该实例相关的缓存
集群的同步下线信息则跟集群信息注册的逻辑差不多
Eureka服务下线源码解析的更多相关文章
- Eureka自我保护机制源码解析
默认情况下,当EurekaServer在一定时间内(默认90秒)没有接收到某个客户端实例的心跳,EurekaServer将会注销该实例.但是当网络分区故障发生时,客户端与EurekaServer之间无 ...
- Netty5服务端源码解析
Netty5源码解析 今天让我来总结下netty5的服务端代码. 服务端(ServerBootstrap) 示例代码如下: import io.netty.bootstrap.ServerBootst ...
- Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器解析)
make解析 服务容器对对象的自动解析是服务容器的核心功能,make 函数.build 函数是实例化对象重要的核心,先大致看一下代码: public function make($abstract) ...
- Spring Cloud Eureka服务注册源码分析
Eureka是怎么work的 那eureka client如何将本地服务的注册信息发送到远端的注册服务器eureka server上.通过下面的源码分析,看出Eureka Client的定时任务调用E ...
- SpringCloud服务调用源码解析汇总
相信我,你会收藏这篇文章的,本篇文章涉及Ribbon.Hystrix.Feign三个组件的源码解析 Ribbon架构剖析 这篇文章介绍了Ribbon的基础架构,也就是下图涉及到的6大组件: Ribbo ...
- Eureka服务端源码流程梳理
一.简述 spring cloud三步走,一导包,二依赖,三配置为我们简化了太多东西,以至于很多东西知其然不知其所以然,了解底层实现之后对于一些问题我们也可以快速的定位问题所在. spring clo ...
- Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器绑定)
服务容器的绑定 bind 绑定 bind 绑定是服务容器最常用的绑定方式,在 上一篇文章中我们讨论过,bind 的绑定有三种: 绑定自身 绑定闭包 绑定接口 今天,我们这篇文章主要从源码上讲解 Ioc ...
- Dubbo服务暴露源码解析②
目录 0.配置解析 1.开始export 2.组装URL 3.服务暴露 疑问解析 先放一张官网的服务暴露时序图,对我们梳理源码有很大的帮助.注:不论是暴露还是导出或者是其他翻译,都是描述expor ...
- Dubbo服务引用源码解析③
上一章分析了服务暴露的源码,这一章继续分析服务引用的源码.在Dubbo中有两种引用方式:第一种是服务直连,第二种是基于注册中心进行引用.服务直连一般用在测试的场景下,线上更多的是基于注册中心的方式 ...
随机推荐
- katalon studio升级到6.3.3版本后如何生成测试报告
背景: katalon studio 6.3.0版本开始,默认不会生成测试报告,因此,原先自动化运行结果的邮件也就不会包含测试报告附件. 解决方法如下: 通过插件[basic reports]生成测试 ...
- xcode 左边导航栏中,类文件后面的标记“A”,"M","?"……等符号的含义???
"M" = Locally modified "U" = Updated in repository "A" = Loc ...
- 实战FFmpeg--编译iOS平台使用的FFmpeg库(支持arm64的FFmpeg2.6.2)
编译环境:Mac OS X 10.10.2 ,Xcode 6.3 iOS SDK 8.3 FFmpeg库的下载地址是 http://www.ffmpeg.org/releases/ . ...
- [20190524]sqlplus 与输出&.txt
[20190524]sqlplus 与输出&.txt --//在sqlplus下 &一般作为参数替换,如何要输出&,一般有几种情况.据说这个问题是asktom站点查看最多的问题 ...
- 文:你可以杀我,但你不能评价(judge)我
原创 豆瓣影评:“现代战争启示录”豆友影评 2006-12-18 20:24:20 本文刊载于<豆瓣影评>豆友“芹泽虾饺菌”的影评 原文标题<剃刀边缘的疯狂> 文/芹泽虾饺菌 ...
- MySQL 主从复制(实时热备)原理与配置
MySQL是现在普遍使用的数据库,但是如果宕机了必然会造成数据丢失.为了保证MySQL数据库的可靠性,就要会一些提高可靠性的技术.MySQL主从复制可以做到实时热备数据.本文介绍MySQL主从复制原理 ...
- Linux的启动过程的分析
Linux的启动过程 Linux系统从启动大哦提供服务的基本过程为:首先机器家电,然后通过MBR或者UEFI装载GRUB,再启动内核,再由内核启动服务,最后开始对外服务 CentOS7要经历四个主要阶 ...
- 自动化部署-svn hook触发构建
目的 之前是通过轮询的形式,2分钟更新一次svn,即时性不高,现在想要实现提交代码时直接触发构建 方案 使用svn的服务器hook,当有代码提交时请求jenkins api实现构建 具体实现 1.je ...
- web前端学习路线(干货)
- NOIP 2011 铺地毯
洛谷 P1003 铺地毯 洛谷传送门 JDOJ 1744: [NOIP2011]铺地毯 D1 T1 JDOJ传送门 Description 为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看 ...