默认情况下,当EurekaServer在一定时间内(默认90秒)没有接收到某个客户端实例的心跳,EurekaServer将会注销该实例。但是当网络分区故障发生时,客户端与EurekaServer之间无法正常通信,此时不应该注销客户端。Eureka通过“自我保护机制”来解决这个问题:当EurekaServer短时间内丢失过多客户端时,这个节点就会进入自我保护模式。在自我保护模式下,EurekaServer不会剔除任何客户端。当网络故障恢复后,该节点会自动退出自我保护模式
自我保护机制的实现是基于维护服务注册表的类 AbstractInstanceRegistry
中的2个变量来维护的
/** * 期望最小每分钟续租次数 */ protected volatile int numberOfRenewsPerMinThreshold; /** * 期望最大每分钟续租次数 */ protected volatile int expectedNumberOfRenewsPerMin;
服务端的启动文章可以看这篇文章: EurekaServer自动装配及启动流程解析
在服务端启动、从其他集群同步完信息后执行了一个方法: openForTraffic
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) { this.expectedNumberOfRenewsPerMin = count * 2; this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()); }
期望每分钟最大续租次数为:当前服务端已经注册的客户端的数量乘2,为啥呢,因为默认Eureka的续约是30秒
期望每分钟最小续租次数为:最大续租次数乘续租百分比,默认续租百分比是0.85,也就是说当某个时间窗内如果存在超过百分之十五的客户端没有再续租的话则开启自我保护模式
DefaultEurekaServerContext
类中有一个 initialize
方法,这个方法在执行过程中会启动一个定时任务
@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 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 { // 1. 计算应用实例数 Applications apps = eurekaClient.getApplications(); int count = 0; for (Application app : apps.getRegisteredApplications()) { for (InstanceInfo instance : app.getInstances()) { if (this.isRegisterable(instance)) { ++count; } } } // 2. 计算expectedNumberOfRenewsPerMin 、 numberOfRenewsPerMinThreshold 参数 synchronized (lock) { 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); } }
分为2步,第一步就不说了,看第二步
当最大续租数量大于最小续租数量时或者没有开启自我保护模式时可以重新计算两个值,否则不能重新计算
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) { synchronized (lock) { if (this.expectedNumberOfRenewsPerMin > 0) { this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2; this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()); } } }
每当有一个实例注册上来时,两个参数都要重新计算,最大期望续租数量+2同样是因为默认1分钟续租2次
public boolean cancel(final String appName, final String id, final boolean isReplication) { synchronized (lock) { if (this.expectedNumberOfRenewsPerMin > 0) { this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2; this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()); } } }
于注册的处理逻辑恰好相反
之前在 Eureka客户端续约及服务端过期租约清理源码解析 一文的租约过期清理解析过程中省略了关于自我保护模式的判断,现在再看一下。这个判断在租约过期处理方法的开头:
public void evict(long additionalLeaseMs) { logger.debug("Running the evict task"); if (!isLeaseExpirationEnabled()) { logger.debug("DS: lease expiration is currently disabled."); return; } //.... }
详细内容在 isLeaseExpirationEnabled
中
public boolean isLeaseExpirationEnabled() { if (!isSelfPreservationModeEnabled()) { return true; } return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold; } public boolean isSelfPreservationModeEnabled() { return serverConfig.shouldEnableSelfPreservation(); } public long getNumOfRenewsInLastMin() { return renewsLastMin.getCount(); }
第一个if是判断是否开启自我保护模式
最后的return则是如果当前最小续租次数大于0,并且最近续约实例数量大于最小期待续租数量