Eureka源码深度刨析-(10)Eureka自我保护机制

“不积跬步,无以至千里。”

eureka专题的第10篇文章,来聊聊eureka所谓的自我保护机制

提起eureka的自我保护机制,相信人人都能说两句,大概的意思,就是eureka在一段时间内,如果过期的服务实例过多,超多了一个比例,eureka server就会认为自己作为注册中心一定是网络发生了故障,导致接收不到客户端的心跳,这个时候就会进入一个保护模式,就不会再摘除任何服务实例了,然后静静地等待自己的网络恢复,巴拉巴拉… …

大概就是这么个意思,这个保护机制,有些地方说生产环境建议开启,看个人情况吧。

直接来看AbstractInstanceRegistryevict方法

上来就会在isLeaseExpirationEnabled()方法里判断一下,判断上一分钟的心跳次数,是否小于我期望的一分钟的心跳次数,如果小于,那么压根儿就不让清理任何服务实例,就直接return了

public void evict(long additionalLeaseMs) {
        logger.debug("Running the evict task");

        if (!isLeaseExpirationEnabled()) {
            logger.debug("DS: lease expiration is currently disabled.");
            return;
        }
     ... ...
}

首先会看一下isSelfPreservationModeEnabled()这个配置,如果是false,也不会开启保护模式,不过默认是ture的,所以如果我们想禁用保护模式,把这个参数设置为false即可

 @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;
 }
@Override
public boolean shouldEnableSelfPreservation() {
    return configInstance.getBooleanProperty(
        namespace + "enableSelfPreservation", true).get();
}

关键的一行代码来了:

numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold

numberOfRenewsPerMinThreshold,就是期望的心跳数,记住这个参数,服务注册,下线,故障摘除,都会重新计算这个值

就是说,首先这个期望的心跳数大于0,如果不大于0,直接返回false,不摘除(没实例摘啥)

然后getNumOfRenewsInLastMin()获取上一分钟实际的心跳数,这个后面分析,如果不大于期望的心跳次数,返回false,不摘除

ok,分析完了这一块代码逻辑,那么这个所谓的“期望心跳数”是在哪里初始化的呢??

答案是:Eureka Server初始化的时候。

还记得上篇文章说的registry.openForTraffic(applicationInfoManager, registryCount)这个方法吗?之前我们说摘除服务实例的逻辑说过,其实计算期望的心跳数的逻辑也在这个方法中。

我把关键的一些代码贴出来,

// Copy registry from neighboring eureka node
int registryCount = registry.syncUp();
registry.openForTraffic(applicationInfoManager, registryCount);

首先 registry.syncUp(),这是从相邻的其他server节点拷贝注册表,然后返回的registryCount就是服务实例的个数,作为了openForTraffic方法的参数之一

@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.expectedNumberOfClientsSendingRenews = count;
    updateRenewsPerMinThreshold();
    ... ...
}

这里把registryCount赋值给了expectedNumberOfClientsSendingRenews这个变量,然后调用了关键的计算期望心跳的逻辑 updateRenewsPerMinThreshold()

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

这个numberOfRenewsPerMinThreshold就是前面说的期望心跳数,我们看看是怎么计算出来的

首先,expectedNumberOfClientsSendingRenews 是注册的实例数,比方说有10个

这个serverConfig.getExpectedClientRenewalIntervalSeconds()是eureka client发送心跳续约的时间间隔,默认是30s,用60/30,得到2,也就是说一个client每秒发送两次心跳,如果你配置的续约周期是60,那么这个结果就是1

最后这个serverConfig.getRenewalPercentThreshold()是续约实例的比例,默认是0.85

所以说10 * 2 * 0.85,最后计算得出,10个服务实例,默认一分钟的期望心跳次数是17次!!!

如果小于这个值,就会开启保护模式,不再下线任何服务实例。

服务注册,下线,故障摘除,都会重新调用这个方法计算这个期望心跳次数的值

比如,随便找一下服务注册的代码

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

expectedNumberOfClientsSendingRenews加1,然后调用updateRenewsPerMinThreshold()方法

如果没猜错,服务下线就是-1了

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

结果跟我们想的完全一样

故障实例摘除,也会调用internalCancel()方法,也是走上面的逻辑,重新计算期望心跳次数的值

最后,我们来看看它怎么计算实际的心跳次数的,getNumOfRenewsInLastMin(),这里面用了一个MeasutredRate的机制

private final MeasuredRate renewsLastMin;
... ...
@com.netflix.servo.annotations.Monitor(name = "numOfRenewsInLastMin",
            description = "Number of total heartbeats received in the last minute", type = DataSourceType.GAUGE)
@Override
public long getNumOfRenewsInLastMin() {
    return renewsLastMin.getCount();
}
private final AtomicLong lastBucket = new AtomicLong(0);
... ...
public long getCount() {
    return lastBucket.get();
}

最终是调用了MeasutredRate的一个getCount()方法,从一个AtomicLong中拿到了收集的一分钟的实例心跳次数

那么这个lastBucket是怎么统计的?

你想想这个所谓的统计心跳跟什么有关,当然是注册表,那么这个MeasutredRate是在哪里被构建和初始化的呢?

答案呼之欲出:注册表初始化的时候

来到AbstractInstanceRegistry这个老生常谈的类看一看吧,就在注册表初始化的时候

protected AbstractInstanceRegistry(EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs) {
    this.serverConfig = serverConfig;
    this.clientConfig = clientConfig;
    this.serverCodecs = serverCodecs;
    this.recentCanceledQueue = new CircularQueue<Pair<Long, String>>(1000);
    this.recentRegisteredQueue = new CircularQueue<Pair<Long, String>>(1000);

    this.renewsLastMin = new MeasuredRate(1000 * 60 * 1);

    this.deltaRetentionTimer.schedule(getDeltaRetentionTask(),
                                      serverConfig.getDeltaRetentionTimerIntervalInMs(),
                                      serverConfig.getDeltaRetentionTimerIntervalInMs());
}

this.renewsLastMin = new MeasuredRate(1000 * 60 * 1),这里new了一个对象,传了一个1分钟的参数进去,这个参数就是控制其内部的调度任务运行的周期,sampleInterval,1分钟哥们!!!

public MeasuredRate(long sampleInterval) {
    this.sampleInterval = sampleInterval;
    this.timer = new Timer("Eureka-MeasureRateTimer", true);
    this.isActive = false;
}
public synchronized void start() {
    if (!isActive) {
        timer.schedule(new TimerTask() {

            @Override
            public void run() {
                try {
                    // Zero out the current bucket.
                    lastBucket.set(currentBucket.getAndSet(0));
                } catch (Throwable e) {
                    logger.error("Cannot reset the Measured Rate", e);
                }
            }
        }, sampleInterval, sampleInterval);

        isActive = true;
    }
}

lastBucket.set(currentBucket.getAndSet(0))

这行代码,是整个MeasuredRate机制的精髓所在,调度任务1分钟执行一次,每次将currentBucket这个当前这一分钟的eureka集群客户端所有心跳次数赋值给lastBucket,然后清空,归零,归零之后就重新统计了,然后evict任务需要获取客户端实际心跳跟期望心跳比对的时候,调用lastBucket.get()即可拿到,后面的事情我们都知道了,如果实际心跳次数小于期望心跳,就会开启保护模式。

而每次客户端调用renew()方法来续约的时候,这个currentBucket就会+1

renewsLastMin.increment();

public void increment() {
    currentBucket.incrementAndGet();
}

整个自我保护机制到这个程度,已经相当的清晰明了了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值