本文主要基于 Eureka 1.8.X 版本
1. 概述
2. 定义
3. 实现
3.1 触发条件
3.2 计算公式
3.3 计算时机
666. 彩蛋
RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
您对于源码的疑问每条留言都将得到认真回复. 甚至不知道如何读源码也可以请教噢.
新的源码解析文章实时收到通知. 每周更新一篇左右.
认真的源码交流微信群.
1. 概述
本文主要分享 自我保护机制, 为应用实例过期下线做铺垫.
推荐 Spring Cloud 书籍:
请支持正版. 下载盗版, 等于主动编写低级 BUG .
程序猿 DD -- 《Spring Cloud 微服务实战》 https://union-click.jd.com/jdc?d=505Twi
周立 -- 《Spring Cloud 与 Docker 微服务架构实战》 https://union-click.jd.com/jdc?d=k3sAaK
2. 定义
自我保护机制定义如下:
FROM 周立 -- 《理解 Eureka 的自我保护模式》
当 Eureka Server 节点在短时间内丢失过多客户端时 (可能发生了网络分区故障), 那么这个节点就会进入自我保护模式. 一旦进入该模式, Eureka Server 就会保护服务注册表中的信息, 不再删除服务注册表中的数据 (也就是不会注销任何微服务). 当网络故障恢复后, 该 Eureka Server 节点会自动退出自我保护模式.
为什么使用自动保护机制 ? 你也可以从周立兄的这篇文章得到答案, 这里笔者就不一本正经的胡说八道了.
3. 实现
首先, 我们来看下在自动保护机制里扮演重要角色的两个变量:
- // AbstractInstanceRegistry.java
- /**
- * 期望最小每分钟续租次数
- */
- protected volatile int numberOfRenewsPerMinThreshold;
- /**
- * 期望最大每分钟续租次数
- */
- protected volatile int expectedNumberOfRenewsPerMin;
- expectedNumberOfRenewsPerMin
, 期望最大每分钟续租次数.
numberOfRenewsPerMinThreshold
, 期望最小每分钟续租次数.
3.1 触发条件
当每分钟心跳次数 ( renewsLastMin ) 小于 numberOfRenewsPerMinThreshold 时, 并且开启自动保护模式开关 ( eureka.enableSelfPreservation = true ) 时, 触发自动保护机制, 不再自动过期租约, 实现代码如下:
- // AbstractInstanceRegistry.java
- public void evict(long additionalLeaseMs) {
- if (!isLeaseExpirationEnabled()) {
- logger.debug("DS: lease expiration is currently disabled.");
- return;
- }
- // ... 省略过期租约逻辑
- }
- // PeerAwareInstanceRegistryImpl.java
- @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;
- }
3.2 计算公式
计算公式如下:
expectedNumberOfRenewsPerMin
= 当前注册的应用实例数 x 2
- numberOfRenewsPerMinThreshold
- =
- expectedNumberOfRenewsPerMin
* 续租百分比 (
- eureka.renewalPercentThreshold
- )
为什么乘以 2
默认情况下, 注册的应用实例每半分钟续租一次, 那么一分钟心跳两次, 因此 x 2 .
这块会有一些硬编码的情况, 因此不太建议修改应用实例的续租频率.
为什么乘以续租百分比
低于这个百分比, 意味着开启自我保护机制.
默认情况下, eureka.renewalPercentThreshold = 0.85 .
如果你真的调整了续租频率, 可以等比去续租百分比, 以保证合适的触发自我保护机制的阀值. 另外, 你需要注意, 续租频率是 Client 级别, 续租百分比是 Server 级别.
3.3 计算时机
目前有四个地方会计算 numberOfRenewsPerMinThreshold , expectedNumberOfRenewsPerMin, 我们逐小节来看.
3.3.1 Eureka-Server 初始化
Eureka-Server 在启动时, 从 Eureka-Server 集群获取注册信息, 并首次初始化 numberOfRenewsPerMinThreshold , expectedNumberOfRenewsPerMin . 实现代码如下:
- // EurekaBootStrap.java
- protected void initEurekaServerContext() throws Exception {
- // ... 省略其它代码
- // [2.2.10] 从其他 Eureka-Server 拉取注册信息
- // Copy registry from neighboring eureka node
- int registryCount = registry.syncUp();
- registry.openForTraffic(applicationInfoManager, registryCount);
- // ... 省略其它代码
- }
- // PeerAwareInstanceRegistryImpl.java
- @Override
- 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());
- // ... 省略其它代码
- }
3.3.2 定时重置
Eureka-Server 定时重新计算 numberOfRenewsPerMinThreshold ,expectedNumberOfRenewsPerMin . 实现代码如下:
- // PeerAwareInstanceRegistryImpl.java
- private void scheduleRenewalThresholdUpdateTask() {
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- updateRenewalThreshold();
- }
- }, serverConfig.getRenewalThresholdUpdateIntervalMs(),
- serverConfig.getRenewalThresholdUpdateIntervalMs());
- }
- // AbstractInstanceRegistry.java
- /**
- * 自我保护机锁
- *
- * 当计算如下参数时使用:
- * 1. {@link #numberOfRenewsPerMinThreshold}
- * 2. {@link #expectedNumberOfRenewsPerMin}
- */
- protected final Object lock = new Object();
- private void updateRenewalThreshold() {
- try {
- // 计算 应用实例数
- Applications apps = eurekaClient.getApplications();
- int count = 0;
- for (Application App : apps.getRegisteredApplications()) {
- for (InstanceInfo instance : App.getInstances()) {
- if (this.isRegisterable(instance)) {
- ++count;
- }
- }
- }
- // 计算 expectedNumberOfRenewsPerMin , numberOfRenewsPerMinThreshold 参数
- synchronized (lock) {
- // Update threshold only if the threshold is greater than the
- // current expected threshold of if the self preservation is disabled.
- if ((count * 2)> (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold)
- || (!this.isSelfPreservationModeEnabled())) {
- this.expectedNumberOfRenewsPerMin = count * 2;
- this.numberOfRenewsPerMinThreshold = (int) ((count * 2) * serverConfig.getRenewalPercentThreshold());
- }
- }
- logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
- } catch (Throwable e) {
- logger.error("Cannot update renewal threshold", e);
- }
- }
配置
eureka.renewalThresholdUpdateIntervalMs
参数, 定时重新计算. 默认, 15 分钟.
代码块
!this.isSelfPreservationModeEnabled()
: 当未开启自我保护机制时, 每次都进行重新计算. 事实上, 这两个参数不仅仅自我保护机制会使用到, 配合 Netflix Servo https://github.com/Netflix/servo 实现监控信息采集
- numberOfRenewsPerMinThreshold
- ,
- expectedNumberOfRenewsPerMin
- .
代码块
(count * 2)> (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold)
: 当开启自我保护机制时, 应用实例每分钟最大心跳数 ( count * 2 ) 小于期望最小每分钟续租次数 (
serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold
), 不重新计算. 如果重新计算, 自动保护机制会每次定时执行后失效.
3.3.3 应用实例注册
应用实例注册时, 增加 numberOfRenewsPerMinThreshold ,expectedNumberOfRenewsPerMin . 实现代码如下:
- //
- public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
- // ... 省略无关代码
- // The lease does not exist and hence it is a new registration
- // [自我保护机制] 增加 `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());
- }
- }
- // ... 省略无关代码
- }
3.3.4 应用实例下线
应用实例下线时, 减少 numberOfRenewsPerMinThreshold ,expectedNumberOfRenewsPerMin . 实现代码如下:
- // PeerAwareInstanceRegistryImpl.java
- @Override
- public boolean cancel(final String appName, final String id,
- final boolean 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());
- }
- }
- // ... 省略无关代码
- }
666. 彩蛋
终于完整理解 Eureka-Server 自我保护机制, 满足. 噶~~
推荐另一篇 Eureka-Server 自我保护机制源码分析文章:《理解 eureka 的自我保护机制》 https://segmentfault.com/a/1190000009795944 .
胖友, 分享我的公众号 ( 芋道源码 ) 给你的胖友可好?
来源: https://juejin.im/entry/5c955b04f265da6112563e58