🌺The Begin🌺点点关注,收藏不迷路🌺

引言:为什么需要自我保护?

想象一下这样的场景:

  • 正常情况:偶尔有1-2个服务因自身故障心跳失败,被Eureka正常剔除
  • 网络抖动:突然所有服务的心跳都收不到了,Eureka该怎么做?

如果没有自我保护:

  • Eureka会认为所有服务都挂了,把整个注册表清空
  • 所有消费者拿不到服务列表,系统彻底瘫痪!

这就是Eureka自我保护机制存在的意义!

错误处理

没有自我保护

剔除所有服务

系统瘫痪

有自我保护

保留所有服务

网络恢复后继续使用

网络故障场景

收不到心跳

收不到心跳

收不到心跳

收不到心跳

Eureka Server

服务1

服务2

服务3

服务4


一、自我保护机制的触发条件 🎯

1.1 核心公式

public class SelfPreservationTrigger {
    
    // 触发条件:15分钟内,心跳失败比例超过15%
    private static final double RENEWAL_PERCENT_THRESHOLD = 0.85; // 85%
    
    public boolean shouldTrigger() {
        // 期望心跳数 = 注册实例数 × 2(每分钟2次心跳)
        long expectedRenews = getInstanceCount() * 2;
        
        // 实际收到的心跳数(最近一分钟)
        long actualRenews = getRenewsInLastMinute();
        
        // 计算心跳率
        double renewalPercent = (double) actualRenews / expectedRenews;
        
        // 如果心跳率低于85%,触发自我保护
        return renewalPercent < RENEWAL_PERCENT_THRESHOLD;
    }
}

1.2 两种不同的故障场景

网络故障

网络故障

网络故障

网络故障

服务A

Eureka

服务B

服务C

自我保护
全部保留

个别服务故障

心跳失败

心跳正常

心跳正常

服务A

Eureka

服务B

服务C

剔除服务A
保留B和C


二、自我保护机制的行为模式 🔄

2.1 自我保护下的Eureka

public class SelfPreservationMode {
    
    // 1. 不再剔除过期服务
    public void evict() {
        if (isSelfPreservationEnabled() && shouldEnterSelfPreservation()) {
            log.info("自我保护模式:跳过服务剔除");
            return;  // 不执行剔除
        }
        doEviction();  // 正常剔除
    }
    
    // 2. 继续接受新服务注册
    public boolean register(InstanceInfo info) {
        // 自我保护期间,依然接受新服务注册
        return doRegister(info);
    }
    
    // 3. 不同步到其他节点
    public void replicateToPeers(InstanceInfo info) {
        if (isSelfPreservationEnabled() && shouldEnterSelfPreservation()) {
            log.info("自我保护模式:暂停集群同步");
            return;  // 不同步,避免错误扩散
        }
        doReplicate(info);  // 正常同步
    }
}

2.2 自我保护期间的状态

{
  "status": "UP",
  "selfPreservation": true,
  "renewalsThreshold": 85,
  "renewalsLastMinute": 30,
  "registeredInstances": 100,
  "expectedRenews": 200,
  "actualRenews": 30,
  "message": "EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD."
}

三、自我保护机制的实现原理 🔧

3.1 核心源码分析

// Eureka Server核心类
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry {
    
    // 自我保护阈值
    private volatile double renewalPercentThreshold = 0.85;
    
    // 最近一分钟的心跳数
    private final MeasuredRate renewsLastMin;
    
    // 检查是否进入自我保护
    @Override
    public boolean isLeaseExpirationEnabled() {
        if (!isSelfPreservationEnabled()) {
            // 如果关闭自我保护,正常处理
            return true;
        }
        
        // 计算期望心跳数
        int numberOfRenewsInThreshold = getNumOfRenewsInLastMin();
        long threshold = (long) (numberOfRenewsInThreshold * renewalPercentThreshold);
        
        // 获取实际心跳数
        long currentRenews = renewsLastMin.getCount();
        
        // 如果实际心跳数大于阈值,可以剔除过期服务
        if (currentRenews > threshold) {
            return true;
        }
        
        // 否则进入自我保护,禁止剔除
        logger.warn("自我保护模式启用,心跳数 {} 低于阈值 {}", 
                   currentRenews, threshold);
        return false;
    }
    
    // 自我保护模式下更新心跳
    public boolean renew(String appName, String id, boolean isReplication) {
        // 即使心跳失败,也返回成功(保护数据)
        if (isSelfPreservationEnabled() && !shouldAllowRenew()) {
            return true;
        }
        return super.renew(appName, id, isReplication);
    }
}

3.2 心跳统计机制

public class MeasuredRate {
    
    private final long sampleInterval;
    private final AtomicLong currentBucket = new AtomicLong(0);
    private volatile long lastBucketChange = System.currentTimeMillis();
    
    // 每1分钟重置统计
    public void increment() {
        long current = currentBucket.incrementAndGet();
        long now = System.currentTimeMillis();
        
        if (now - lastBucketChange > sampleInterval) {
            // 重置计数器
            currentBucket.set(0);
            lastBucketChange = now;
        }
    }
    
    public long getCount() {
        return currentBucket.get();
    }
}

四、自我保护机制的配置 ⚙️

4.1 核心配置参数

# Eureka Server配置
eureka:
  server:
    # 开启自我保护(默认true)
    enable-self-preservation: true
    
    # 自我保护阈值(默认0.85)
    renewal-percent-threshold: 0.85
    
    # 心跳失败次数阈值
    renewal-threshold-update-interval-ms: 900000  # 15分钟
    
    # 剔除任务间隔(自我保护期间不执行)
    eviction-interval-timer-in-ms: 60000  # 60秒
    
    # 自我保护期间的心跳阈值
    expected-client-renewal-interval-seconds: 30
    response-cache-update-interval-ms: 30000

4.2 不同环境的配置建议

# 生产环境(开启自我保护)
eureka:
  server:
    enable-self-preservation: true
    renewal-percent-threshold: 0.85

# 开发/测试环境(可关闭自我保护)
eureka:
  server:
    enable-self-preservation: false  # 方便测试服务剔除
    eviction-interval-timer-in-ms: 5000  # 5秒剔除一次

五、自我保护机制的优缺点 📊

5.1 优点

优点 说明 类比
防止误剔 避免网络抖动导致大量服务被误删 股市熔断机制
保证可用 保留可能不健康的数据,也比没有数据好 天气预报报有雨,总比不报好
平滑恢复 网络恢复后自动退出保护,数据无缝衔接 电脑休眠唤醒

5.2 缺点

缺点 说明 影响
数据不准 可能保留已挂的服务 消费者可能调用失败
资源浪费 不剔除过期服务 注册表越来越大
延迟恢复 退出保护后需要时间同步 短暂的不一致

六、自我保护机制的工作流程 🔄

6.1 完整流程图

正常模式

连续15分钟
心跳率<85%?

进入自我保护

正常剔除过期服务

停止服务剔除

继续接受新注册

暂停集群同步

网络是否恢复?

退出自我保护

重新开始剔除

集群同步

6.2 自我保护日志示例

# 进入自我保护
2024-01-01 10:00:00 WARN  [com.netflix.eureka] 
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. 
RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

# 自我保护期间
2024-01-01 10:05:00 INFO  [com.netflix.eureka] 
Self preservation mode enabled. Current renewal threshold: 85.0%

# 自我保护解除
2024-01-01 10:30:00 INFO  [com.netflix.eureka] 
Self preservation mode disabled. Network may be stable again.

# 开始恢复剔除
2024-01-01 10:30:05 INFO  [com.netflix.eureka] 
Eviction task started, 10 expired instances will be removed.

七、自我保护机制的实践经验 💡

7.1 生产环境最佳实践

# 生产环境推荐配置
eureka:
  server:
    # 开启自我保护(必须)
    enable-self-preservation: true
    
    # 适当调低阈值,提高容错性
    renewal-percent-threshold: 0.75  # 75%
    
    # 延长统计周期
    renewal-threshold-update-interval-ms: 1800000  # 30分钟
    
    # 监控自我保护状态
    response-cache-auto-expiration-in-seconds: 180

7.2 监控告警配置

@Component
public class SelfPreservationMonitor {
    
    @Autowired
    private EurekaServerConfig config;
    
    @Scheduled(fixedDelay = 60000)
    public void monitorSelfPreservation() {
        boolean inSelfPreservation = isInSelfPreservation();
        
        if (inSelfPreservation) {
            long duration = getSelfPreservationDuration();
            
            if (duration > 30 * 60 * 1000) { // 超过30分钟
                // 发送告警:自我保护时间过长
                alert("Eureka自我保护已持续30分钟,请检查网络!");
            }
            
            // 记录心跳率
            double renewalPercent = getRenewalPercent();
            log.warn("自我保护中,心跳率:{}%", renewalPercent * 100);
        }
    }
}

7.3 客户端适配

@Configuration
public class ClientSelfPreservationAdapter {
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    // 自我保护期间,增加重试机制
    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();
        
        // 指数退避重试
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        backOffPolicy.setInitialInterval(1000);
        backOffPolicy.setMaxInterval(10000);
        backOffPolicy.setMultiplier(2);
        retryTemplate.setBackOffPolicy(backOffPolicy);
        
        // 重试3次
        retryTemplate.setRetryPolicy(
            new SimpleRetryPolicy(3)
        );
        
        return retryTemplate;
    }
}

八、总结:自我保护的哲学 🎯

8.1 核心思想

“保留可能不正确的数据,总比没有数据要好”

8.2 一句话总结

/**
 * 自我保护机制 = 
 *   网络故障检测 + 
 *   服务保留策略 + 
 *   自动恢复机制
 */
public class SelfPreservationSummary {
    
    public String explain() {
        return "当网络抖动时,Eureka宁愿保留可能已挂的服务," +
               "也不愿清空注册表导致系统瘫痪。这就像开车时" +
               "宁愿相信导航可能不准,也不愿关掉导航瞎开。";
    }
}

8.3 自我保护的价值

场景 没有自我保护 有自我保护
网络抖动5分钟 注册表清空,系统瘫痪 注册表完整,服务正常
网络恢复后 需要重建注册表 立即恢复正常
系统可用性 99.9% 99.99%

(本文为微服务架构系列文章,欢迎关注更多分布式系统深度内容)

在这里插入图片描述


🌺The End🌺点点关注,收藏不迷路🌺
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐