原创不易,转载注明出处

系列文章目录

  1. 《SpringCloud Eureka Server源码解析(启动流程)》
  2. 《Eureka Server源码解析(服务注册流程)》
  3. 《Eureka Server源码解析(服务续约流程)》
  4. 《Eureka Server源码解析(服务主动下线流程)》
  5. 《深度解析Eureka的自我保护机制》

文章目录

  • 系列文章目录
  • 前言
  • 1.源码解析
    • 1.1 创建故障剔除任务
    • 1.2 服务剔除流程
      • 1.2.1 自我保护机制
      • 1.2.2 服务剔除
  • 2.流程图
  • 总结

前言

本文主要是解析下Euraka Server 是怎样剔除那些“失联”服务实例的源码,一个服务注册到Eureka Server 上后,默认会30s 发送一次服务续约请求告诉Eureka Server 自己还活着,如果一个服务突然挂了,并没有主动向Eureka Server 发送服务下线请求,并没有从注册表删除该实例信息,其他服务拉取注册表的时候还能拉取到这个实例信息,并且使用的有问题,对于这种突然“失联”的服务实例,Eureka Server 是怎样做的呢? 服务故障剔除,Eureka Server 启动的时候,会启动一个定时任务,默认是每60s 扫描一次注册表,看看哪些服务实例长时间没有发送续约请求,就会将这些服务从注册表中剔除,默认是90s 没有续约的服务实例(但实际上90+90s ,在那段判断逻辑中,有段注释说该处是bug ,但是他并不改, 这里是我们使用Eureka 作为注册中心的时候需要注意的一个点)

1.源码解析

1.1 创建故障剔除任务

在Eureka Server 初始化启动完成后,会创建一个服务剔除定时任务,默认每个60s扫描一次注册表,我们来看看定时任务创建流程代码。
在EurekaBootStrap 的initEurekaServerContext 方法最后面有 registry.openForTraffic(applicationInfoManager, registryCount); 这行代码
它会改变Eureka Server 服务状态为UP,在它UP之后有一行,调用父类postInit方法。

applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
super.postInit();

也就是注册表父类AbstractInstanceRegistry 的postInit方法

protected void postInit() {renewsLastMin.start();if (evictionTaskRef.get() != null) {evictionTaskRef.get().cancel();}evictionTaskRef.set(new EvictionTask());evictionTimer.schedule(evictionTaskRef.get(),serverConfig.getEvictionIntervalTimerInMs(),serverConfig.getEvictionIntervalTimerInMs());}

这个方法干了2件事情,1是启动了一个定时任务,用作每分钟的服务续约renew 统计,2是启动一个服务剔除定时任务,默认是60s执行一次。

public long getEvictionIntervalTimerInMs() {return configInstance.getLongProperty(namespace + "evictionIntervalTimerInMs", (60 * 1000)).get();
}

可以看到,如果你不指定的话,默认是60执行一次。

1.2 服务剔除流程

在1.1小节中,我们看了服务剔除定时任务的创建,执行的任务是EvictionTask ,接下来我们看下它是怎样服务故障剔除的。

private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l);
@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);}
}

getCompensationTimeMs 这个方法防止服务器时钟发生问题,做出的时间补偿,这里我们不需要过多的关注。
调用了evict 方法进行服务剔除,方法比较长,我们一部分一部分的看下

1.2.1 自我保护机制

if (!isLeaseExpirationEnabled()) {logger.debug("DS: lease expiration is currently disabled.");return;
}

是否启用服务故障剔除。

@Override
public boolean isLeaseExpirationEnabled() {if (!isSelfPreservationModeEnabled()) {// The self preservation mode is disabled, hence allowing the instances to expire.return true;}return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}

先是判断配置开没开启故障剔除(默认是开启的),然后再判断是否触发了自我保护机制。
这里我解释下自我保护机制是怎样触发的, 比如说每一个新的实例注册到Eureka Server ,Eureka 都会记录下来当前注册了多少个实例,再就是默认客户端每30s进行服务续约一次,一分钟1个实例就是2次服务续约,它都会记录下来,按照这个规则,如果我有10个实例注册到了Eureka Server 上,那么按照正常流程下,每分钟就会有20个服务续约请求,当然这是最好的情况下,不好的情况下服务续约请求可能达不到20次,这个时候它就有一个最小阈值,也就是85% ,这里就是需要最少有15 次服务续约,当低于这个阈值的时候,就会自动开启自我保护机制,这次服务剔除就不会再进行。关于Eureka Server自我保护机制 详细计算规则请查看我的另一篇文章《深度解析Eureka的自我保护机制》

1.2.2 服务剔除

再接着往下

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);}}}
}

可以看到这里直接遍历注册表,遍历每一个实例信息,调用每个实例租约的lease.isExpired(additionalLeaseMs) 方法判断有没有过期,其中additionalLeaseMs是上面计算的补偿时间。

规则就是:当前时间 大于 上次续约计算出来的时间 + duration(默认是90s)+ 补偿时间。

上次续约计算出来的时间 :

public void renew() {lastUpdateTimestamp = System.currentTimeMillis() + duration;
}

说白了就是 当前时间 距离服务上次续约时间超过 90+90 s,超过180s认为过期了。
过期的实例信息会被塞到一个集合中。

// 注册表中所有实例信息
int registrySize = (int) getLocalRegistrySize();
// 85%
int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
// 计算剔除范围, 就是不超过注册表所有实例的15%
int evictionLimit = registrySize - registrySizeThreshold;
// 剔除数量不超过注册表实例的15%
int toEvict = Math.min(expiredLeases.size(), evictionLimit);

这段代码就是计算出来要剔除服务实例的数量,默认是不超过注册表所有实例数量的15% ,比如说我注册表中有100个实例,但是上面发现有20个实例过期了,但是根据这个规则,这次服务剔除,只能剔除15个。

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);}
}

上面这段代码就是真正的服务剔除了,根据上面的规则,只能剔除15个服务实例,所以就遍历15次。服务剔除走的服务下线逻辑,并且不进行集群节点间的同步。

2.流程图

总结

Eureka Server 初始化完成后,会启动一个定时任务,定时扫描注册表中(默认是60s 扫描一次),专门清除那些长时间没有续约(发送心跳)的客户端实例,将实例信息从注册表中剔除(默认是2倍的duration ,一个duration默认是90s,180s没有发送心跳的服务实例会被从注册表中剔除,),但是需要注意的是,自我保护机制触发后不会剔除这些服务。

Eureka Server源码解析(服务故障下线剔除流程)相关推荐

  1. 【SpringCloud系列】服务注册与发现 - Eureka Server源码分析(2)

    3.6.Eureka Server 源码分析 上一篇文章简单介绍了 Eureka 的一些概念,今天咱们来看看其原理和源码,首先先看 Eureka Server 的原理. 3.6.1.Eureka Se ...

  2. FileZilla Server源码解析之LIST命令

    FileZilla Server源码解析之LIST命令 如需转载请标明出处:http://blog.csdn.net/itas109 QQ技术交流群:129518033 FileZilla版本:Fil ...

  3. Zabbix 源码解析之监控项数据采集流程

    一.概述 监控项数据采集是一个监控工具最基本的功能,监控数据采集的准确.实时.有效是Zabbix其它监控功能正常运转的前提.因此,Zabbix运维人员有必要了解监控项数据采集流程,并有针对性的设计巡检 ...

  4. Spring Cloud微服务系列-Eureka Client源码解析(一)

    导语   Eureka Client 是为了简化开发人员的开发工作,将很多的Eureka Server交互的工作进行了封装,在使用的时候自动完成,在应用的不同阶段来完成不同的功能实现.下面就来了解一下 ...

  5. Spring Cloud微服务系列-Eureka Client源码解析(二)

    导语   上一篇博客中介绍了关于Eureka Client源码的基础部分,如果对于基础部分不是很了解的读者可以点击下面的连接进入到源码分析一中,从头开始学习 Spring Cloud微服务系列 Dis ...

  6. 深入理解Eureka之源码解析

    Eureka的一些概念 Register:服务注册 当Eureka客户端向Eureka Server注册时,它提供自身的元数据,比如IP地址.端口,运行状况指示符URL,主页等. Renew:服务续约 ...

  7. 从源码解析-结合Activity加载流程深入理解ActivityThrad的工作逻辑

    ActivityThread源码解析 前言 类简称 类简介 一 二 三 四 五 代理和桩的理解 ActivityThread ActivityThread.main AT.attach AMN.get ...

  8. Spring源码解析(五)-Bean的实例化流程(上)

    在前面已经完成了对需要实例化bean的收集并封装成BeanDefinition,并且将BeanPostProcess等组件进行了提前实例化.接下来就到了容器启动的最后一步,也是最复杂的一步-实例化be ...

  9. FlinkSQL源码解析(三)执行流程

    1.前言 前面2部分主要是介绍以下2点: flink sql整体的执行流程大致为:sqlNode --> Operation --> RelNode --> 优化 --> ex ...

最新文章

  1. 比特币现金对穷人更友善
  2. bnuoj 20950 沉重的货物 (最小生成树)
  3. 怎么说呢。留个纪念,关于字符串的重载
  4. Protobuf序列化的原理
  5. Web框架——Flask系列之Flask创建app对象 路由(十二)
  6. 行为设计模式 - 命令设计模式
  7. notepad怎么设置python为环境变量_在Notepad++里配置python环境
  8. 使用根轨迹分析的动态补偿设计
  9. 【Matlab】 读取文件各种方法
  10. labview rtm_下载Vista RTM
  11. 市场调查有什么好的选题推荐么?
  12. java编程填空及答案_JAVA填空题标准答案(103题)
  13. 9.无 线 网 络 和 移 动 网 络
  14. 测试第一步,雀氏纸尿裤
  15. 1T数据到底有多大?
  16. 【魅力开源】第3集:聊个轻松点的话题,如何把给员工发生日贺卡这个事变得有人情味点?关于Odoo的自动化邮件系统
  17. 苹果笔记本硬盘替换方案
  18. excel如何实现数据钻取
  19. 在VS2010下如何使用头文件unistd.h
  20. solr读取word,pdf

热门文章

  1. eNSP:当电脑从win10升级到win11,却发现启动虚拟设备(路由或防火墙)时出现报错,报错代码为40,如何解决?
  2. 【开发技巧】调教Win10自带的微软拼音输入法的快捷键
  3. windows11安装jdk8
  4. 程序员 《相见恨晚的超实用网站》
  5. Java入门-8-【入门】将小写字母转换为大写字母 【运算符】
  6. citespace导入数据去重后生成文件中各个指标的含义
  7. Tabbar组件封装
  8. 死亡观察:这13家知名公司何时消失
  9. 查看电脑异常关机日志
  10. 车牌字符识别HyperLPR中端到端的字符识别方法