Eureka 是 Spring Cloud Netflix 的服务注册与发现工具。一般情况下,它都能很好的工作,但有时却会出现一些匪夷所思的情况。今天我们就来研究一下不当的配置导致的幽灵服务。

幽灵出现

我们先来给幽灵服务做一个定义:它们是 Eureka 注册表中的一些节点,但是它们实际上已经被关闭了,永远无法访问。

我们可以通过 GET /eureka/apps 获取 Eureka 中注册的节点信息,其中包含了节点的最后更新时间:lastRenewalTimestamp 。如果发现这个时间已经过去了很久,那么这个节点可能就是一个幽灵节点。

这其实是一个非常反常的现象,因为 Eureka client 在关闭时,通过 @PreDestroy 触发 DiscoveryClient.shutdown() 方法,向 Eureka Server 发送 shutdown 请求,从 Eureka Server 注册表中注销。

那么问题来了。

为什么这些服务没有注销?

因为 @PreDestroy 是用来处理 SIGTERM 等信号的,所以只有在通过这些信息关闭 Eureka client 时,Eureka client 才会向 Eureka Server 发送 shutdown 请求。但是如果是通过 kill -9 这样的指令,或者是 不优雅的 stop docker,即发送 SIGKILL 信号,都不会触发 PreDestroy,那么这些 Eureka client 也就不会被注销了。

那么问题又来了。

难道 Eureka 不会清理过期的注册信息吗?

其实是会的。

在 Eureka Server 的核心库 eureka-core 中,有一个 AbstractInstanceRegistry 类,其中实现了 evict() 方法,用来完成清理过期注册信息的工作。这个方法被一个 TimerTask 定时调用,默认间隔时间是 60s。

在这个方法中,Eureka server 会:

  1. 找到所有过期的 Lease (对应一个 Eureka client 节点)
  2. 通过本地注册的 Lease 数量和一个阈值 (后面还有戏份) 计算出一个 evictonLimit 数值
  3. 比较过期的 Lease 数量和 evictionLimit 的大小,选出小的那个数作为本次清理的数量
  4. 从过期的 List 中随机挑出 n 个 Lease,删除 (这里的 n 就是上一步的结果)

这里的阈值通过 eureka.server.renewal-percent-threshold 设置,默认值是 0.85。

后面限制每次清理节点数量的算法,只是一种性能优化的考虑。如果忽略掉这部分的逻辑,我们可以认为,一个节点长时间没有向 Eureka server 发送心跳请求,那么它的 Lease 就会过期,最终被定时任务清楚掉。

因为默认的租期时间是 90s,evict 周期是 60s,所以一个服务被不优雅的关闭后,最多经过 120s 就可以被清理出注册表。

但是 eureka 在这里却有一个 bug[1]。Eureka server 在接收到心跳请求时,会把当前时间加上租期时间作为最后接收到心跳的时间。然后在清理时,又会在这个时间上再加一个租期时间来判断是否过期。所以一个服务过期时间总共是 180s,它被不优雅的关闭后最多经过 240s 就会被清理掉。

但是事情可能没有那么简单,别说 240s,就是过了两天,这些幽灵服务可能还在你的注册表里。

这就涉及到另一个问题,Eureka server self-preservation mode.

自我保护模式

概念

Eureka client 之间调用时,不会向 Eureka server 发送任何请求,而是根据本地维护的注册表,找到需要调用的服务,直接调用,也就是 peer to peer 的模式。本地的注册表,其实是从 Eureka server 拉取回来的,默认每 30s 拉取一次更新。那么当 Eureka server 因为网络震荡没有接收到某些 Eureka client 发送的心跳请求时,并不意味着 Eureka client 之间的网络也出现了问题,Eureka client 之间可能仍然能够访问。如果这个时候更新注册表,清理了只是因为网络震荡而没有发送心跳的节点,那么本可能成功的调用就会失败。为了减少对服务的影响,Eureka 默认启动了自我保护模式,不会清理掉过期的注册信息。

官方详细的解释可以看这里[2]

自我保护的逻辑

在上面介绍的 evict 方法的逻辑之前,eureka 会首先调用 isLeaseExpirationEnabled 方法,以判断是否要执行后续的清理逻辑。

这个方法首先会判断是否启用了自我保护模式,如果没有启用,那么就返回 true,否则会返回下面这个表达式的结果。

number-of-renews-per-min-threshold > 0   && num-of-renews-in-last-min > number-of-renews-per-min-threshold

那么接下来我们就看看这两个值是什么。

Number of Renews in Last Minute

这个值的含义顾名思义,就是过去一分钟内,eureka server 接收到的心跳请求次数。这不是实时数据,而是每分钟更新一次。具体逻辑被 MeasureRate 实现。

由于 eureka client 默认配置的心跳间隔是 30s,所以这默认情况下的这个值就是 2*client-size

Number of Renews per Minute Threshold

这个值就相对复杂一些,表示的是触发自我保护模式的心跳阈值。根据表达式,就明白它的含义:当过去一分钟实际接收到的心跳总数小于等于这个心跳阈值时,就会触发自我保护模式,Eureka server 就不会清理注册信息。

接着,我们来看看这个阈值是如何计算出来的:

(int) (expected-number-of-clients-sending-renews   * (60.0 / expected-client-renewal-interval-seconds)  * renewal-percent-threshold)

好了,这里又冒出来三个值,我们先来看看后两个。

Expected Client Renewal Interval Seconds

这是一个配置的值,默认 30s,通过 eureka.server.expected-client-renewal-interval-seconds 配置。

需要注意的是,这个值不是 client 真实的发送心跳的时间间隔,真实的心跳间隔是值 client 中配置的 eureka.instance.lease-renewal-interval-in-seconds,同样默认 30s。

但这两个值显然应该保持一致,因为上面的 number-of-renews-in-last-min 其实就是 (60 / lease-renewal-interval-in-seconds) * client-size,而这里也几乎是同样的逻辑(如果把 expected-number-of-clients-sending-renews 看作 client-size 的话)。

Renewal Percent Threshold

这个值在前面讲清理注册信息的时候已经讲过了,默认值 0.85。

Expected Number of Clients Sending Renews

根据名字,这个值代表的是期望的会发送心跳请求的 client 数量,也就是前面的 client-size。但是实际上的值却有可能不同。

这个值的一个更新时间是启动 eureka server 时。Eureka server 会尝试从配置的邻居 server 节点拉取注册表信息来配置。如果没有邻居节点,或者注册表中没有信息的话,就会被设置为默认值 1,通过 eureka.server.defaultOpenForTrafficCount 配置。

不是很明白为什么要设置默认值为 1。不过相关代码中,在注册与注销时,都只有在这个值大于 0 时才会更新。

这个值的另一个更新时间是一个定时任务,默认每 15min 执行一次。可以通过 eureka.server.renewal-threshold-update-interval-ms 修改这个时间。

最后,每当这个值被更新之后,都会更新我们的心跳阈值。

了解了上面的这些逻辑,我们了解到,如果符合 应有的心跳数 - 失去的心跳数 ≤ 心跳阈值,那么就会触发自我保护机制。

处理一个幽灵服务的极限值

了解了前面这些,我不禁产生一个疑问:在默认配置的情况下,有多少个 client 之后,eureka server 才会清理掉过期的注册信息?出现多少个过期的注册信息,就会触发自我保护模式?

这其实只是一个数学问题。假设集群中一共有 n 个 client,eureka server 能接受的不会触发自我保护模式的过期注册信息个数有 m 个,那么我们就可以有如下的推导:

threshold = (n + default-open-for-traffic-count) * (60 / expect-client-renewal-interval-second) * renewal-percent-threshold// 带入默认的配置之后=> threshold = (n + 1) * (60 / 30) * 0.85 = 1.7(n + 1)// 因为每 30s 发送一次心跳,所以 n 个服务就应该有 2n 个心跳, m 个幽灵服务就会失去 2m 个心跳2n - 2m ≤ 1.7(n + 1)=> 2n - 1.7(n + 1) ≤ 2m=> 0.15n - 0.85 ≤ m

所以,一个集群如果要能够处理一个过期的注册信息,也就是 m=1, 那么至少需要有多于 12.33 个 eureka client,也就是 13 个。换句话说,在默认配置下,如果集群中只有不到 13 个服务,那么任何一个服务被不优雅的关闭,都会出现幽灵服务。

验证

为了验证上面的结论,我写了个 demo[3] 来验证一下。

为了保证每分钟的 evict 方法都用到最新的心跳阈值,我把 expected-number-of-clients-sending-renews 的更新时间设置为了 60s。

这个 demo 用了 docker-compose,验证的步骤需要启动 14 个 container,想要试试的同学需要给自己的 docker 多分配点资源。

12 个 client 会触发自我保护模式

话不多说,直接启动

docker-compose up -d eureka-clinet=12

稍等一会儿,访问 http://localhost:8080 就可以在 eureka 的页面上看到两个值:

  • Renews threshold: 22
  • Renews (last min): 24

这两个值都符合上面的数学计算。

然后,随便杀掉一个 client (一定要使用 docker kill,避免 client 发送 shutdown 请求):

docker kill eureka-self-preservation-verify_eureka-client_1

然后几分钟过去了,eureka server 的注册服务仍然有 12 个。

13 个 client 能够处理掉一个幽灵服务

还是上面的命令,只是这次我们启动 13 个 eureka-client

然后也是随便杀掉一个 client,等待结果。

被杀掉的节点被清理前:

被杀掉的节点被清理后:

还可以观察一下杀掉两个 client 的效果:

同时,也可以利用 GET /eureka/apps 查看注册信息,关注被杀掉的节点的 lastRenewalTimestamp 信息。这是它的最后注册时间,因为加了 90s,所以应该是一个未来的时间。用这个时间加上 90s,在这之后的第一次 evict 就应该清理掉这个节点信息,这可以在 eureka-server 的日志中看到。

总结

我们研究了 eureka 的自我保护模式,它的目的是避免在出现网络震荡时删除掉可能正常工作的节点信息。但如果配置不当,自我保护模式反而可能产生幽灵服务。

可以影响到自我保护模式的配置主要有两个:

  • eureka.server.enable-self-preservation 默认为 true
  • eureka.server.renewal-percent-threshold 默认为 0.85

根据实际的情况,我们可能需要直接关闭自我保护模式,或者找出一个更适合具体情况的 threshold。

参考资料

[1]

bug: https://github.com/Netflix/eureka/issues/531

[2]

Self Preservation: https://github.com/Netflix/eureka/wiki/Server-Self-Preservation-Mode

[3]

demo: https://github.com/kbyyd24/eureka-self-preservation-verify

eureka 之前的服务如何关闭_Eureka 中的幽灵相关推荐

  1. eureka 之前的服务如何关闭_干货分享 | 服务注册中心Spring Cloud Eureka部分源码分析...

    友情提示:全文13000多文字,预计阅读时间10-15分钟 Spring Cloud Eureka作为常用的服务注册中心,我们有必要去了解其内在实现机制,这样出现问题的时候我们可以快速去定位问题.当我 ...

  2. eureka 之前的服务如何关闭_支付宝、iPhone、微信的自动续费服务在哪?我们要如何关闭?...

    我们在使用手机时或多或少都会充一些会员,比如视频会员什么的,但你知道吗?这些充值都是自动续费,可时间长了我们或许都忘了这件事,这也导致之后我们每月都会莫名其妙的被扣钱. 那么我们要如何关闭这些自动续费 ...

  3. 如何禁用计算机的服务,怎么关闭掉电脑中的哪些无用服务

    WIN8系统电脑中有哪些无用服务可以关闭掉?我们知道一些服务对电脑的运行速度是有影响的,因此不少人会问Win8电脑哪些服务可以关闭?其实电脑可以关闭的服务很多,如果你不知道的话,W下面小编就和大家分享 ...

  4. 计算机中哪些服务是可以禁止的,win10电脑哪些服务可以关闭?

    win10电脑哪些服务可以关闭?? 大家熟悉一些服务对电脑的运行速度是有影响的,因此不少人会问win10电脑哪些服务可以关闭?其实电脑可以关闭的服务许多,如果你不熟悉的话,那就赶紧来看看小编整理的以下 ...

  5. eureka 集群失败的原因_Eureka集群的那些坑

    今天遇到一个Eureka集群的一个坑. 问题现场类似是这样的:两台Eureka组成的服务注册中心集群,两台服务提供方server1.server2,两个服务调用方client1.client2. 正常 ...

  6. SpringCloud之Eureka(微服务注册)

    文章目录 前言 Peer to Peer架构 主从复制 对等复制 Zone及Region设计 SELF PRESERVATION 一.创建pom文件(工程) 1. 创建父级pom工程 2. 创建Eur ...

  7. Eureka 注册中心/服务发现框架

    Eureka 注册中心/服务发现框架 Eureka注册中心/服务发现框架 如何使用构建 Eureka Server ? 加入依赖(此处以Maven为例) 创建Eureka Server 主运行类 单机 ...

  8. centos7 搭建本地git_本地服务调用K8S环境中的SpringCloud微服务实战

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:原创文章分类汇总及配套源码,涉及Java.Docker.K8S.Devops等 下图是典型的微 ...

  9. linux设置开机服务自动启动/关闭自动启动命令

    [root@localhost~]#chkconfig--list显示开机可以自动启动的服务 [root@localhost~]#chkconfig--add***添加开机自动启动***服务 [roo ...

最新文章

  1. 神童、数学家、抑郁症患者,控制论之父诺伯特·维纳的一生
  2. 大学毕业了,你是否需要需要职业化培训!
  3. 【正一专栏】运动式的创建文明城市要着干嘛
  4. 算法导论之有关数论的算法
  5. 关于Integer类中parseInt()和valueOf()方法的区别以及int和String类性的转换.以及String类valueOf()方法...
  6. android插件框架机制的选择,Android插件开发初探——基础篇
  7. Asp.Net中几种相似数据绑定标记符号的解释及用法
  8. Foundationd和Application Kit的类层次
  9. mysql读写分离-借助中间键mycat
  10. 如何开发一个网页版的SQL查询工具
  11. 育网校园云盘,私有云盘,电子备课系统。
  12. CnPack源码模板功能快速添加注释
  13. 游戏运行时,WIN2003报错:设备 \Device\Harddisk0有一个不正确的区块。
  14. 深入浅出讲解梯度消失和梯度爆炸问题(原因、解决方法)
  15. SolidWorks打开step.文件显示模板无效的解决办法
  16. git pull git_Git Pull解释
  17. PLC培训班一般多少钱?
  18. Jenkins maven自动发布配置
  19. 【unity学习笔记-如何给动态的人物添加碰撞体】
  20. 微信小程序API-设备- 网络状态

热门文章

  1. 《C程序设计语言》笔记 (三) 控制流
  2. SCWS中文分词,功能函数实例应用
  3. ZOJ 1094 带括号的矩阵连乘
  4. 设计模式学习摘要-抽象工厂
  5. tomcat常见报错
  6. linux下的文件及目录介绍
  7. 简单的SQL语句 DDL
  8. 解决Spring对静态变量无法注入问题(转)
  9. Python Selenium之异常处理
  10. win10系统中photoshop cs6中界面字体太小的解决方法