服务下线的大致流程图

  下面这张图很简单地描述了服务剔除的大致流程:

服务剔除实现源码分析

  首先我们得了解下服务剔除这个定时任务是什么被初始化启动的,在百度搜索中,在我们Eureka Server端启用的时执行的EurekaBootStrap类中initEurekaServerContext方法找到了服务剔除任务的初始化。接下来我们就看一看源码:

protected void initEurekaServerContext() throws Exception {
...省略其他代码
registry.openForTraffic(applicationInfoManager, registryCount);
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}

  在initEurekaServerContext()方法中, registry.openForTraffic(applicationInfoManager, registryCount)这个方法来初始化我们的服务剔除任务。我们看源码验证下:

@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
super.openForTraffic(applicationInfoManager,
count == 0 ? this.defaultOpenForTrafficCount : count);
}
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// Renewals happen every 30 seconds and for a minute it should be a factor of 2.
this.expectedNumberOfRenewsPerMin = count * 2;
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
logger.info("Got {} instances from neighboring DS node", count);
logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
this.startupTime = System.currentTimeMillis();
if (count > 0) {
this.peerInstancesTransferEmptyOnStartup = false;
}
DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
boolean isAws = Name.Amazon == selfName;
if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
logger.info("Priming AWS connections for all replicas..");
primeAwsReplicas(applicationInfoManager);
}
logger.info("Changing status to UP");
applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
super.postInit();
}

  在openForTraffic方法中最后我们看到调用了父类postInit()方法,我们接着看postInit这个方法:

protected void postInit() {
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
evictionTaskRef.set(new EvictionTask());
// 开启定时任务,默认60秒执行一次,用于清理60秒之内没有续约的实例
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}

  由上面可见,Eureka通过evictionTimer.schedule初始化了一个定时60s的定时任务。

  接下来我们来看看EvictionTask这个类的具体实现EvictionTask这个类实现了服务剔除的具体操作。

@Override
public void run() {
try {
long compensationTimeMs = getCompensationTimeMs();
logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
evict(compensationTimeMs);
} catch (Throwable e) {
logger.error("Could not run the evict task", e);
}
}

  我们接着看evict()方法的实现:

public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
return;
}
// We collect first all expired items, to evict them in random order. For large eviction sets,
// if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
// the impact should be evenly distributed across all applications.
// 先收集过期的实例信息,然后再剔除掉
List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
if (leaseMap != null) {
for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
Lease<InstanceInfo> lease = leaseEntry.getValue();
if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
expiredLeases.add(lease);
}
}
}
}
// To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
// triggering self-preservation. Without that we would wipe out full registry.
// 为了补偿GC暂停或本地时间漂移,我们需要使用当前注册表大小作为触发自我保护的基础。没有它,我们就会把整个注册表都抹掉。
int registrySize = (int) getLocalRegistrySize();
int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
int evictionLimit = registrySize - registrySizeThreshold; int toEvict = Math.min(expiredLeases.size(), evictionLimit);
if (toEvict > 0) {
logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit); Random random = new Random(System.currentTimeMillis());
for (int i = 0; i < toEvict; i++) {
// Pick a random item (Knuth shuffle algorithm)
int next = i + random.nextInt(expiredLeases.size() - i);
Collections.swap(expiredLeases, i, next);
Lease<InstanceInfo> lease = expiredLeases.get(i); String appName = lease.getHolder().getAppName();
String id = lease.getHolder().getId();
EXPIRED.increment();
logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
internalCancel(appName, id, false);
}
}
}

  由此可见,evict()方法最终实现了服务的剔除。

\(\color{red}{注意:}\)

  \(\color{red}{Eureka的服务剔除会因为Eureka的自我保护机制而受到影响,导致不会剔除掉已经认为下线的服务}\),这一点,会在下一节中做下解Eureka自我保护机制的讲解。

   不知道有没有小伙伴对Eureka是如何判断这个实例是否不可用呢,有很大的疑惑呢?我们接下来去看一看lease.isExpired(additionalLeaseMs)这个方法,这个方法就是拿来判断实例是否可用。


/**
* Checks if the lease of a given {@link com.netflix.appinfo.InstanceInfo} has expired or not.
*
* Note that due to renew() doing the 'wrong" thing and setting lastUpdateTimestamp to +duration more than
* what it should be, the expiry will actually be 2 * duration. This is a minor bug and should only affect
* instances that ungracefully shutdown. Due to possible wide ranging impact to existing usage, this will
* not be fixed.
*
* @param additionalLeaseMs any additional lease time to add to the lease evaluation in ms.
*/
public boolean isExpired(long additionalLeaseMs) {
return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));
}

  右上可见,我们可以发现Eureka是通过lastUpdateTimestamp这个上次更新时间来判断我们的服务是否可用,不知道小伙伴对服务续约哪里有影响,每当我们Client调用一次Server端服务续约接口时,Server端就会更新下服务的lastUpdateTimestamp。我们来回一下服务续约更新上次更新时间的方法,更新lastUpdateTimestamp代码如下:

   /**
* Renew the lease, use renewal duration if it was specified by the
* associated {@link T} during registration, otherwise default duration is
* {@link #DEFAULT_DURATION_IN_SECS}.
*/
public void renew() {
lastUpdateTimestamp = System.currentTimeMillis() + duration; }

   不知道小伙伴有没有注意一个事情,在isExpired这个方法的注释里,好像有一个很大的“彩蛋”,注释如下:Note that due to renew() doing the 'wrong" thing and setting lastUpdateTimestamp to +duration more than what it should be, the expiry will actually be 2 * duration. This is a minor bug and should only affect instances that ungracefully shutdown. Due to possible wide ranging impact to existing usage, this will not be fixed. 翻译过来就是:注意,由于renew()做了“错误”的事情,并将lastUpdateTimestamp设置为+duration,超过了它应该的值,因此到期实际上是2 * duration。这是一个小错误,应该只影响那些不正常关闭的实例。由于可能对现有的使用产生广泛的影响,这个问题将不会得到解决。

   简单来说,就是在服务续约执行renew()方法时,不应该加上duration这个值,但是呢,因为这个问题只会出现在检测不正常关闭的服务才会有影响,Eureka 官方怕其他正在运行的服务有影响,就没有修正这个小error。

  看到这儿,小伙伴是不是觉得,eureka的RD也是很神奇,明明知道这是一个bug,但是却不改(其实人家也想改,但是怕一改影响了其他的正常使用,然后考虑这个bug对Eureka正常使用没有太大影响,也就没有去修正了,但是人家RD还是很贴心的,在注释中还是说明这个问题,以及为什么不修正的原因)。


题外

  可能有小伙伴会问,我们有服务下线接口,为什么还需要EurekaServer服务端自己启用一个服务剔除任务呢?

  其实很简单,因为如果我们是直接强制性停止任务,例如机器停电之类的,肯定Client就不会去调用服务下线接口,来通知Server端自己下线。其次如果我们Client正常停止,在调用服务下线接口中,发现网络出现问题,没法调用Server提供的接口,那样也没法让Server知道自己这个服务下线了。所以Server端需要自己启动一个服务剔除任务,来剔除掉哪些已经down掉的服务。(该观点为博主自己的主观观点,小伙伴也可以自行思考

Eureka系列(八)服务剔除具体实现的更多相关文章

  1. Eureka系列(五) 服务续约流程具体实现

    服务续约执行简要流程图   下面这张图大致描述了服务续约从Client端到Server端的大致流程,详情如下: 服务续约Client源码分析   我们先来看看服务续约定时任务的初始化.那我们的服务续约 ...

  2. Eureka系列(七) 服务下线Server端具体实现

    服务下线的大致流程图   下面这张图很简单地描述了Server端服务下线的大致流程: 服务下线Server端实现源码分析   Eureka服务实现是通过Server端InstanceResource ...

  3. Eureka系列(二) 服务注册Server端具体实现

    服务注册 Server端流程   我们先看下面这张图片,这张图片简单描述了下我们EurekaClient 在调用EurekaServer 提供的服务注册Http接口,Server端实现接口执行的大致流 ...

  4. 转载:Eureka 开发时快速剔除失效服务

    原文地址:https://www.cnblogs.com/flying607/p/8494568.html 服务端配置: # 关闭保护机制 eureka.server.enable-self-pres ...

  5. Eureka 开发时快速剔除失效服务

    Spring Cloud 版本: Dalston.SR5 服务端配置: # 关闭保护机制 eureka.server.enable-self-preservation=false #剔除失效服务间隔 ...

  6. 【SpringCloud微服务实战学习系列】服务治理Spring Cloud Eureka

    Spring Cloud Eureka是Spring Cloud Netflix微服务中的一部分,它基于NetFlix Sureka做了二次封装,主要负责完成微服务架构中的服务治理功能. 一.服务治理 ...

  7. Eureka 系列(07)服务注册与主动下线

    Eureka 系列(07)服务注册与主动下线 [TOC] Spring Cloud 系列目录 - Eureka 篇 在上一篇 Eureka 系列(05)消息广播 中对 Eureka 消息广播的源码进行 ...

  8. eureka源码--服务的注册、服务续约、服务发现、服务下线、服务剔除、定时任务以及自定义注册中心的思路

    微服务注册后,在注册中心的注册表结构是一个map: ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>& ...

  9. 在 Apache 上使用网络安全服务(NSS)实现 HTTPS--RHCE 系列(八)

        在 Apache 上使用网络安全服务(NSS)实现 HTTPS--RHCE 系列(八) 发布:linux培训 来源:Linux认证 时间:2015-12-21 15:26 分享到: 达内lin ...

随机推荐

  1. 你还在 if...else?代码这样写才好看!

    前言 if...else 是所有高级编程语言都有的必备功能.但现实中的代码往往存在着过多的 if...else.虽然 if...else 是必须的,但滥用 if...else 会对代码的可读性.可维护 ...

  2. zabbix agent 编译安装

    zabbix 安装包下载地址 https://www.zabbix.com/download 解压好之后进入zabbix目录 执行编译安装 ./configure --prefix=/usr/loca ...

  3. Django踩坑记录1

    from django.db import models # Create your models here. class Event(models.Model): name = models.Cha ...

  4. IDEA创建web工程,不用Archetype(超简单)

    Idea不用Archetype创建Web项目 以新建模块为例. 新建Maven项目 不勾选[Create from artchetype],直接Next pom中添加一句话: <artifact ...

  5. Mybatis【2.3】-- Mybatis一定要使用commit才能成功修改数据么?

    代码直接放在Github仓库[https://github.com/Damaer/Mybatis-Learning],mybatis-02可直接运行,就不占篇幅了. 为什么我们有时候不使用commit ...

  6. ERP制造模块操作与设计--开源软件诞生30

    赤龙ERP制造模块讲解--第30篇 用日志记录"开源软件"的诞生 [进入地址 点亮星星]----祈盼着一个鼓励 博主开源地址: 码云:https://gitee.com/redra ...

  7. 【mq读书笔记】消息确认(失败消息,定时队列重新消费)

    接上文的集群模式,监听器返回RECONSUME_LATER,需要将将这些消息发送给Broker延迟消息.如果发送ack消息失败,将延迟5s后提交线程池进行消费. 入口:ConsumeMessageCo ...

  8. 【证书】curl 和 java 请求报证书错误

    1. 说明: 以下:例子的域名因为工作环境的问题,被我拿自己的博客域名替代了,所以无法进行模拟测试,请珍重,哈哈! 2. 环境: centos:7.5 java jdk:1.8.0_74 3. cur ...

  9. 【2020.11.28提高组模拟】T2 序列(array)

    序列(array) 题目描述 ​给定一个长为 \(m\) 的序列 \(a\). 有一个长为 \(m\) 的序列 \(b\),需满足 \(0\leq b_i \leq n\),\(\sum_{i=1}^ ...

  10. Docker实战 | 第三篇:Docker安装Nginx,实现基于vue-element-admin框架构建的项目线上部署

    一. 前言 在上一文中 点击跳转 通过IDEA集成Docker插件实现微服务的一键部署,但 youlai-mall 是前后端分离的项目,除了后端微服务的部署之外,当然还少不了前端工程的部署.所以本篇讲 ...