本文主要基于 Eureka 1.8.X 版本
1. 概述
2. 定义
3. 实现
3.1 触发条件
3.2 计算公式
3.3 计算时机
RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
您对于源码的疑问每条留言都将得到认真回复甚至不知道如何读源码也可以请教噢
新的源码解析文章实时收到通知每周更新一篇左右
认真的源码交流微信群
1. 概述
本文主要分享 自我保护机制, 为应用实例过期下线做铺垫
推荐 Spring Cloud 书籍:
请支持正版下载盗版, 等于主动编写低级 BUG
程序猿 DD Spring Cloud 微服务实战
周立 Spring Cloud 与 Docker 微服务架构实战
推荐 Spring Cloud 视频:
Java 微服务实践 - Spring Boot
Java 微服务实践 - Spring Cloud
Java 微服务实践 - Spring Boot / Spring Cloud
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 实现监控信息采集
- 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());
- }
- }
- // ... 省略无关代码
- }
来源: https://juejin.im/entry/5aa1b2c2f265da239c7af171