Spring Cloud 十一:Eureka自我保护源码分析

Eureka自我保护

默认情况下,当EurekaServer在一定时间内(默认90秒)没有接收到某个客户端实例的心跳,EurekaServer将会注销该实例。
但是当网络分区故障发生时,客户端与EurekaServer之间无法正常通信,此时不应该注销客户端。
Eureka通过“自我保护机制”来解决这个问题:当EurekaServer短时间内丢失过多客户端时,这个节点就会进入自我保护模式。
在自我保护模式下,EurekaServer不会剔除任何客户端。当网络故障恢复后,该节点会自动退出自我保护模式

自我保护机制的实现是基于维护服务注册表的类AbstractInstanceRegistry中的2个变量来维护的

public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    // 每分钟最小续约阈值
    protected volatile int numberOfRenewsPerMinThreshold;
    // 预期中发送续约消息的客户端数量
    protected volatile int expectedNumberOfClientsSendingRenews;
}

Eureka Server启动

Eureka Server启动的源码可以去看这篇文章:Spring Cloud 三:Eureka Server源码详细分析

public class EurekaServerBootstrap {

	// 省略部分代码

	protected void initEurekaServerContext() throws Exception {

		// 集群同步
		int registryCount = this.registry.syncUp();
        // 自我保护初始化
        // 服务定时自动剔除。
		this.registry.openForTraffic(this.applicationInfoManager, registryCount);
	}
    
    // 省略部分代码
}

在服务端启动、从其他集群同步完信息后执行了一个方法:openForTraffic

@Override
    public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
        // 初始化期望client发送过来的服务数量,即上面获取到的服务数量
        this.expectedNumberOfClientsSendingRenews = count;
        //计算自我保护的统计参数
        updateRenewsPerMinThreshold();
        this.startupTime = System.currentTimeMillis();
        //   如果count=0,没有拉取到注册表信息,将此值设为true,表示其他peer来取空的实例信息,意味着,将不允许client从此server获取注册表信息。如果count>0,将此值设置为false,允许client来获取注册表。
        if (count > 0) {
            this.peerInstancesTransferEmptyOnStartup = false;
        }
        // 服务置为上线
        applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
        // 开启剔除的定时任务
        super.postInit();
    }

预期中发送续约消息的客户端数量(expectedNumberOfClientsSendingRenews),等于syncUp()集群同步获取到的服务个数。用于下面updateRenewsPerMinThreshold()方法来计算每分钟最小续约阈值

	//每分钟最小续约阈值
	//包含所有客户端,不分服务提供方和消费方
	protected volatile int numberOfRenewsPerMinThreshold;

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

翻译如下:

每分钟最小续约阈值 = 预期中发送续约消息的客户端数量 * (60s / 每分钟客户端预期续约间隔) * 续约百分比阈值
  • 每分钟最小续约阈值: Eureka Server 每分钟至少要收到这个数量的续约消息(心跳)。
  • 预期中发送续约消息的客户端数量 :注册到 Eureka Server 的客户端的数量
  • 60s / 每分钟客户端预期续约间隔:一分钟内,一个客户端的续约次数。每分钟客户端预期续约间隔,默认是 30s。所以一分钟内,一个客户端的续约次数默认是2次
  • 续约百分比阈值:默认 85%。

自我保护定时任务

DefaultEurekaServerContext类中有一个initialize方法,这个方法在执行过程中会启动一个定时任务

public class DefaultEurekaServerContext implements EurekaServerContext {
    @PostConstruct
    @Override
    public void initialize() {
        logger.info("Initializing ...");
        peerEurekaNodes.start();
        try {
            registry.init(peerEurekaNodes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        logger.info("Initialized");
    }
}
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
    @Override
    public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
        this.numberOfReplicationsLastMin.start();
        this.peerEurekaNodes = peerEurekaNodes;
        initializedResponseCache();
        scheduleRenewalThresholdUpdateTask();
        initRemoteRegionRegistry();

        try {
            Monitors.registerObject(this);
        } catch (Throwable e) {
            logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", e);
        }
    }
}

scheduleRenewalThresholdUpdateTask这个定时任务就是跟自我保护模式相关的了

private void scheduleRenewalThresholdUpdateTask() {
        timer.schedule(new TimerTask() {
                           @Override
                           public void run() {
                               updateRenewalThreshold();
                           }
                       }, serverConfig.getRenewalThresholdUpdateIntervalMs(),
                serverConfig.getRenewalThresholdUpdateIntervalMs());
    }

其中getRenewalThresholdUpdateIntervalMs默认值是15分钟

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;
                    }
                }
            }
            // 
            synchronized (lock) {
                // 仅当阈值大于当前的预期阈值或禁用自我保护时才更新阈值。
                // 更新 预期中发送续约消息的客户端数量 expectedNumberOfClientsSendingRenews
                // 更新 每分钟最小续约阈值numberOfRenewsPerMinThreshold
                if ((count) > (serverConfig.getRenewalPercentThreshold() * expectedNumberOfClientsSendingRenews)
                        || (!this.isSelfPreservationModeEnabled())) {
                    // 更新 预期中发送续约消息的客户端数量
                    this.expectedNumberOfClientsSendingRenews = count;
                    // 更新 每分钟最小续约阈值(numberOfRenewsPerMinThreshold)
                    updateRenewsPerMinThreshold();
                }
            }
        }
    }

服务注册

每当有一个实例注册上来时,预期中发送续约消息的客户端数量(expectedNumberOfClientsSendingRenews),和每分钟最小续约阈值(numberOfRenewsPerMinThreshold)都要更新。

public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
        
        // 省略部分代码...
        
        if (this.expectedNumberOfClientsSendingRenews > 0) {
            // 新注册上来的,所以原值+1
            this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
             updateRenewsPerMinThreshold();
        }
    }
}

服务下线

每当有一个实例下线时,预期中发送续约消息的客户端数量(expectedNumberOfClientsSendingRenews),和每分钟最小续约阈值(numberOfRenewsPerMinThreshold)都要更新。

public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    protected boolean internalCancel(String appName, String id, boolean isReplication) {
        
        // 省略部分代码...
        
        if (this.expectedNumberOfClientsSendingRenews > 0) {
           // 一个服务下线了,所以要减去一个
           this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
           updateRenewsPerMinThreshold();
        }
    }
}

开启自我保护模式

在服务端定时剔除过期服务时,会判断是否允许服务剔除,如果开启了自我保护或者当前服务续约个数 > 最小续约个数允许服务剔除

public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    public void evict(long additionalLeaseMs) {
        // 是否允许服务剔除,如果允许服务剔除则走下面的服务剔除逻辑,如果不允许服务剔除则返回
        if (!isLeaseExpirationEnabled()) {
            logger.debug("DS: lease expiration is currently disabled.");
            return;
        }
        
        // 省略部分代码...
    }
}
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
    /**
     * 是否允许服务剔除,没开自我保护,或者开启了自我保护,然而实际续约个数>最小续约个数,所以允许服务剔除。
     */
    @Override
    public boolean isLeaseExpirationEnabled() {
        // 检查是否启用了自我保护
        if (!isSelfPreservationModeEnabled()) {
            return true;
        }
        return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
    }
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

吃透Java

你的鼓励是我最大的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值