Eureka心跳机制与自动保护机制原理分析

Eureka心跳机制:

在应用启动后,节点们将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
 

Eureka自动保护机制:

Eureka Server在运行期间会去统计心跳成功的比例在15分钟之内是否低于85% , 如果低于85%, Eureka Server会认为当前实例的客户端与自己的心跳连接出现了网络故障,那么Eureka Server会把这些实例保护起来,让这些实例不会过期导致实例剔除。这样做就是为了防止EurekaClient可以正常运行, 但是与EurekaServer网络不通情况下, EurekaSerJer不会立刻将EurekaClient服务剔除

这样做的目的是为了减少网络不稳定或者网络分区的情况下,Eureka Server将健康服务剔除下线的问题。 使用自我保护机制可以使得Eureka 集群更加健壮和稳定的运行。进入自我保护状态后,会出现以下几种情况:Eureka Server不再从注册列表中移除因为长时间没有收到心跳而应该剔除的过期服务;Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上,保证当前节点依然可用。

自我保护模式:

默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例 (默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过" 自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。

一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。在Spring Cloud中,可以使用eureka.server.enable-self-preservation = false 禁用自我保护模式。

开启自我保护机制:通过配置将判定时间改为10s,接着启动Eureka Server,等待10s之后,就会出现以上提示信息,表示自我保护被激活了。

# 设置 eureka server同步失败的等待时间默认5分钟

#在这期间,它不向客户端提供服务注册信息

eureka.server.wait-time-in-ms-when-sync-empty=10000

重要的变量

在Eureka的自我保护机制中,有两个很重要的变量,Eureka的自我保护机制,都是围绕这两个变量来实现的,在AbstractInstanceRegistry这个类中定义的:

**numberOfRenewsPerMinThreshold**

protected volatile int numberOfRenewsPerMinThreshold;

//每分钟最小续约数量就是Eureka Server期望每分钟收到客户端实例续约的总数的阈值。如果小于这个阈值,就会触发自我保护机制。

它的赋值方法:

protected void updateRenewsPerMinThreshold() {
        this.numberOfRenewsPerMinThreshold = (int)((double)this.expectedNumberOfClientsSendingRenews * (60.0D / (double)this.serverConfig.getExpectedClientRenewalIntervalSeconds()) * this.serverConfig.getRenewalPercentThreshold());
    }

getExpectedClientRenewalIntervalSeconds,客户端的续约间隔,默认为30s;

getRenewalPercentThreshold,自我保护续约百分比阈值因子,默认0.85,即每分钟的续约数量要大于85%。

需要注意的是,这两个变量是动态更新的,有四个地方来更新这两个值。

# Eureka-Server的初始化

EurekaBootstrap类中的initEurekaServerContext方法会对Eureka-Server进行初始化:

protected void initEurekaServerContext() throws Exception {
EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();
//...
registry.openForTraffic(applicationInfoManager, registryCount);
}

PeerAwareInstanceRegistryImpl类中的openForTraffic方法:

public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
    // Renewals happen every 30 seconds and for a minute it should be a factor of 2.
    this.expectedNumberOfClientsSendingRenews = count; //初始化
    this.updateRenewsPerMinThreshold();
    //更新numberOfRenewsPerMinThreshold
    logger.info("Got {} instances from neighboring DS node", count);
    logger.info("Renew threshold is: {}", this.numberOfRenewsPerMinThreshold);
    this.startupTime = System.currentTimeMillis();
    if (count > 0) {
        this.peerInstancesTransferEmptyOnStartup = false;
    }
    Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
    boolean isAws = Name.Amazon == selfName;
    if (isAws && this.serverConfig.shouldPrimeAwsReplicaConnections()) {
        logger.info("Priming AWS connections for all replicas..");
        this.primeAwsReplicas(applicationInfoManager);
    }
    logger.info("Changing status to UP");
    applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
    super.postInit();
}

# 服务的主动下线

PeerAwareInstanceRegistryImpl类中的cancel方法:

当服务提供者主动下线时,表示这个时候Eureka-Server要剔除这个服务提供者的地址,同时也代表这这个心跳续约的阈值要发生变化。所以在PeerAwareInstanceRegistryImpl.cancel 中可以看到数据的更新

调用路径

PeerAwareInstanceRegistryImpl.cancel -> AbstractInstanceRegistry.cancel->internalCancel

服务下线之后,意味着需要发送续约的客户端数量递减了,所以在这里进行修改

protected boolean internalCancel(String appName, String id, boolean isReplication) {
//....
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// Since the client wants to cancel it, reduce the number of clients to send renews.
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
updateRenewsPerMinThreshold();
}
}
}

# 服务的注册

PeerAwareInstanceRegistryImpl类中的register方法:

当有新的服务提供者注册到eureka-server上时,需要增加续约的客户端数量,所以在register方法中会进行处理

register ->super.register(AbstractInstanceRegistry)

public void register(InstanceInfo info, boolean isReplication) {
    int leaseDuration = 90;
    if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
        leaseDuration = info.getLeaseInfo().getDurationInSecs();
    }
    super.register(info, leaseDuration, isReplication);
    this.replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Register, info.getAppName(), info.getId(), info, (InstanceStatus)null, isReplication);
}

父类AbstractInstanceRegistry中的register方法:

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {

//....
// The lease does not exist and hence it is a new registration synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// Since the client wants to register it, increase the number of clients sending renews
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
updateRenewsPerMinThreshold();
}
}
}

PeerAwareInstanceRegistryImpl类中的scheduleRenewalThresholdUpdateTask方法(更新心跳):

15分钟运行一次,判断在15分钟之内心跳失败比例是否低于85%。在

DefaultEurekaServerContext -> @PostConstruct修饰的initialize()方法 -> init()

private void scheduleRenewalThresholdUpdateTask() {
        this.timer.schedule(new TimerTask() {
            public void run() {
                PeerAwareInstanceRegistryImpl.this.updateRenewalThreshold();
            }
        }, (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs(), (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs());
    }

private void updateRenewalThreshold() {
        try {
            Applications apps = this.eurekaClient.getApplications();
            int count = 0;
            Iterator var3 = apps.getRegisteredApplications().iterator();
            while(var3.hasNext()) {
                Application app = (Application)var3.next();
                Iterator var5 = app.getInstances().iterator();
                while(var5.hasNext()) {
                    InstanceInfo instance = (InstanceInfo)var5.next();
                    if (this.isRegisterable(instance)) {
                        ++count;
                    }
                }
            }
// Update threshold only if the threshold is greater than the
// current expected threshold or if self preservation is disabled. 
            synchronized(this.lock) {
                if ((double)count > this.serverConfig.getRenewalPercentThreshold() * (double)this.expectedNumberOfClientsSendingRenews || !this.isSelfPreservationModeEnabled()) {
                    this.expectedNumberOfClientsSendingRenews = count;
                    this.updateRenewsPerMinThreshold();
                }
            }

            logger.info("Current renewal threshold is : {}", this.numberOfRenewsPerMinThreshold);
        } catch (Throwable var9) {
            logger.error("Cannot update renewal threshold", var9);
        }
    }

# 自我保护机制触发任务

在AbstractInstanceRegistry的postInit方法中,会开启一个EvictionTask的任务,这个任务用来检测是否需要开启自我保护机制:

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

其中,EvictionTask表示最终执行的任务:

class EvictionTask extends TimerTask {
    private final AtomicLong lastExecutionNanosRef = new AtomicLong(0L);
    EvictionTask() {
    }
    public void run() {
        try {
            long compensationTimeMs = this.getCompensationTimeMs();
            AbstractInstanceRegistry.logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
            AbstractInstanceRegistry.this.evict(compensationTimeMs);
        } catch (Throwable var3) {
            AbstractInstanceRegistry.logger.error("Could not run the evict task", var3);
        }
    }
    long getCompensationTimeMs() {
        long currNanos = this.getCurrentTimeNano();
        long lastNanos = this.lastExecutionNanosRef.getAndSet(currNanos);
        if (lastNanos == 0L) {
            return 0L;
        } else {
            long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);
            long compensationTime = elapsedMs - AbstractInstanceRegistry.this.serverConfig.getEvictionIntervalTimerInMs();
            return compensationTime <= 0L ? 0L : compensationTime;
        }
    }
    long getCurrentTimeNano() {
        return System.nanoTime();
    }
}


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

evict

public void evict(long additionalLeaseMs) {
     logger.debug("Running the evict task");
    // 是否需要开启自我保护机制,如果需要,那么直接return,不需要继续往下执行了
    if (!isLeaseExpirationEnabled()) {
    logger.debug("DS: lease expiration is currently disabled."); return;
}

//下面主要是做服务自动下线的操作的

isLeaseExpirationEnabled方法:判断是否开启了自我保护机制,如果没有,则跳过,默认是开启。计算是否需要开启自我保护,判断最后一分钟收到的续约数量是否大于numberOfRenewsPerMinThreshold

public boolean isLeaseExpirationEnabled() {

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

<think>好的,用户现在想了解Eureka保护机制及其工作原理。根据用户提供的引用内容,我需要整理这些信息,并结合之前的回答,给出一个清晰的解释。首先,我需要确认用户提到的几个引用内容之间的关系,以及如何将它们整合到回答中。 用户提供的引用1到5主要涉及Eureka的核心概念、自我保护机制、集群原理心跳机制以及配置方法。特别是引用2、3、5详细说明了自我保护机制的开启关闭、工作原理以及相关代码实现。用户的问题集中在保护机制如何工作及其原理,所以需要重点围绕这些方面展开。 首先,我应该从自我保护机制的定义开始,说明其目的,比如防止因网络问题导致服务被错误注销。然后,结合引用3提到的,当Eureka Server节点在短时间内丢失过多客户端心跳时,会触发自我保护模式,此时不会删除服务注册信息。需要解释触发条件和进入保护模式后的行为。 接下来,需要详细说明心跳机制自我保护的关系,引用3和5提到Eureka Server通过心跳来判断服务是否存活,如果心跳失败比例超过阈值,则触发保护。这里需要明确阈值计算方式,比如每分钟的心跳续约次数,默认是15分钟内低于85%则触发。 然后,结合引用5中的代码片段,说明Eureka Server初始化的过程,特别是初始化上下文时如何开启流量接收,这可能保护机制的启动有关。这部分可能需要更深入的技术细节,比如Eureka Server的配置类和相关方法。 用户可能还关心如何配置自我保护机制,引用2和3提到在配置文件中设置enable-self-preservation参数,可以开启或关闭该机制。需要提醒用户在生产环境中不建议关闭,并解释原因,如引用3提到的网络恢复后自动退出保护模式,保障稳定性。 此外,引用1和4提到Eureka作为服务注册发现的工具,分为Server和Client,保护机制属于Server端的重要功能,确保注册中心的稳定性和高可用性。需要强调自我保护机制在集群中的作用,比如在集群环境下,各节点同步数据,保护机制如何协调工作。 还要考虑用户可能的疑问,比如保护机制开启后,服务是否还能注册或注销,引用3指出保护模式下不会注销服务,但新服务仍可以注册。同时,客户端缓存服务列表,即使Server进入保护模式,客户端仍能使用旧列表进行调用,避免整个系统崩溃。 最后,需要总结自我保护机制的设计哲学,如引用3提到的“宁可保留所有服务”的理念,确保在网络不稳定时系统的健壮性。同时,对比关闭保护机制的风险,强调默认开启的重要性。 在整理过程中,要注意引用正确的来源,使用用户提供的引用编号,比如[^3]、[^5]等,确保信息准确。同时,保持回答结构清晰,分点阐述触发条件、核心原理、行为变化、设计哲学和配置建议,帮助用户逐步理解。 可能还需要预判用户的后续问题,比如如何监控自我保护状态,或如何处理保护模式下的服务故障,但根据当前问题,先集中在工作原理上。需要避免过于技术化的术语,用易懂的语言解释机制,并结合实际场景说明其重要性。 总之,整合所有引用信息,按照逻辑顺序分步骤解释自我保护机制的工作原理,确保回答全面且符合用户需求。</think>根据提供的引用资料和Eureka的设计原理,我将从**3个核心维度**解析保护机制的工作原理,并配合流程图说明运行机制: --- ### **一、自我保护机制触发条件** #### **1. 心跳健康度检测** - **计算公式**: $$ 心跳丢失率 = \frac{最近15分钟丢失心跳数}{预期心跳总数} $$ 当丢失率超过阈值(默认15分钟内低于85%心跳)时触发保护[^3][^5] - **动态阈值算法**: ```java // Eureka-Server源码中的计算逻辑 protected boolean isLeaseExpirationEnabled() { return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold; } ``` 其中`numberOfRenewsPerMinThreshold`根据注册服务数量动态计算[^5] #### **2. 网络分区感知** 当同时满足以下条件时判定为网络分区: 1. 当前存活实例数 > 预期最小存活数(默认0) 2. 心跳续约率 < 保护阈值(默认0.85) --- ### **二、保护模式核心行为** #### **1. 注册表冻结** | 正常模式 | 保护模式 | |-------------------------|-------------------------| | 定期清理超时实例(默认90秒) | 停止清理所有实例 | | 新实例可正常注册 | 新实例仍然接受注册 | | 客户端获取实时服务列表 | 客户端使用缓存列表[^1] | #### **2. 客户端应对策略** 1. 客户端本地缓存服务列表(默认每30秒更新) 2. Ribbon负载均衡器使用增量更新策略 3. 客户端定时重试心跳检测(默认间隔30秒) --- ### **三、退出保护机制流程** ```mermaid graph TD A[网络恢复] --> B{连续5分钟<br>心跳达标} B -- Yes --> C[自动关闭保护模式] C --> D[清理过期实例] B -- No --> E[维持保护状态] ``` --- ### **四、配置建议注意事项** #### **1. 生产环境最佳实践** ```yaml # 推荐配置(application.yml) eureka: server: renewal-percent-threshold: 0.85 # 触发阈值 enable-self-preservation: true # 必须开启 eviction-interval-timer-in-ms: 60000 # 清理间隔 instance: lease-expiration-duration-in-seconds: 90 # 客户端心跳超时时间 ``` #### **2. 风险规避方案** - **服务雪崩预防**:配合Hystrix熔断机制使用 - **监控指标**: - `eureka.server.numOfRenewsInLastMin`(最近一分钟续约数) - `eureka.server.peerInstancesTransferSize`(集群同步数据量) --- ### **五、设计哲学解析** 1. **CAP原则取舍**:选择AP模式(高可用+分区容忍),放弃强一致性[^1] 2. **Fail-Safe设计**:宁可保留过期实例,也不丢失可能存活的实例[^3] 3. **双层容错机制**: - 客户端缓存(Client Side Caching) - 服务端保护(Server Side Preservation) --- ### **六、特殊场景处理** #### **1. 集群环境下的保护机制** ```java // Eureka Server集群同步代码片段 public void register(InstanceInfo info) { // 本地注册 registry.register(info, false); // 向其他节点复制 replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication); } ``` 即使单个节点进入保护模式,集群仍可通过节点间数据同步保持可用性[^4] #### **2. 混合云场景** 当跨云网络出现分区时: 1. 每个区域的Eureka集群独立进入保护模式 2. 前端负载均衡器(如AWS ALB)切换流量到健康区域 3. 区域恢复后通过`/v2/peerreplication`端点重建数据 --相关问题-- 1. 如何监控Eureka自我保护机制的触发状态? 2. 在Kubernetes环境中如何优化Eureka保护机制? 3. EurekaNacos在服务保护机制上有哪些本质区别?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肆〇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值