Spring Cloud Netflix-Eureka(三)、自我保护机制

Spring Cloud Netflix之Eureka源码系列文章一共分为六个片段

Spring Cloud Netflix-Eureka(一)、服务注册与发现
Spring Cloud Netflix-Eureka(二)、信息存储原理
Spring Cloud Netflix-Eureka(三)、自我保护机制
Spring Cloud Netflix-Eureka(四)、心跳续约机制
Spring Cloud Netflix-Eureka(五)、多级缓存机制
Spring Cloud Netflix-Eureka(六)、集群数据同步

一、什么是自我保护机制

官方对于自我保护机制的定义:

自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使 Eureka 集群更加的健壮、稳定的运行。

自我保护机制的工作机制是:如果每分钟内超过 renewalPercentThreshold (自我保护续约百分比阈值因子,默认0.85)的客户端节点都没有收到心跳请求,那么 Eureka 就认为客户端与注册中心出现了网络故障,Eureka Server 自动进入自我保护机制,此时会出现以下几种情况:

  1. Eureka Server 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
  2. Eureka Server 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
  3. 当网络稳定时,当前 Eureka Server 新的注册信息会被同步到其它节点中。

因此 Eureka Server 可以很好的应对因网络故障导致部分节点失联的情况,而不会应该网络延迟波动导致服务被误杀。

自我保护机制主要包含两个点:

  1. 每分钟心跳续约数:默认是30s一个心跳包,也就是默认心跳约数为2
  2. 心跳续约阈值:自我保护续约百分比阈值因子,默认0.85

二、那些场景下会触发阈值的重新计算

根据上面的自我保护机制阈值计算公式可知,在服务的上线、下线等,只要涉及到服务数量的变化,都会触发阈值的重新计算。Eureka 默认 15min(可通过renewalThresholdUpdateIntervalMs进行配置) 也会定时更新一次阈值。

三、多久检测一次自我保护机制

在 Eureka Server 中,默认60s进行一次自我保护机制的检测,判断是否要开启自我保护机制,可通过 eipBindRebindRetryIntervalMsWhenUnbound 进行配置。

四、核心代码逻辑

4.1 自动续约阈值初始化

Eureka Server 在启动的时候,会通过 EurekaServerAutoConfiguration 注入一个 EurekaServerBootstrap,负责来初始化 Eureka Server的上下文。

@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
	@Bean
	public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
			EurekaServerContext serverContext) {
		return new EurekaServerBootstrap(this.applicationInfoManager,
				this.eurekaClientConfig, this.eurekaServerConfig, registry,
				serverContext);
	}
}

EurekaServerAutoConfiguration 类实现了 WebMvcConfigurer 接口,关于WebMvcConfigurer 可以参考【SpringBoot-WebMvcConfigurer详解】。

EurekaServerAutoConfiguration 类中还有一个很重要的注解 @Import ,该注解会向 Spring 容器中注入当前实例,即向 IoC 容器中注入 EurekaServerInitializerConfiguration。

4.1.1 EurekaServerInitializerConfiguration

EurekaServerInitializerConfiguration 实现了 Spring中的 SmartLifecycle 接口,Spring 会在容器初始化完成后,调用所有实现当前接口类的 start() 方法,完成 Eureka Server 的初始化工作,具体代码如下。

@Configuration(proxyBeanMethods = false)
public class EurekaServerInitializerConfiguration
		implements ServletContextAware, SmartLifecycle, Ordered {
	@Override
	public void start() {
		new Thread(() -> {
			try {
				// TODO: is this class even needed now?
				// 初始化
				eurekaServerBootstrap.contextInitialized(
						EurekaServerInitializerConfiguration.this.servletContext);
				log.info("Started Eureka Server");
				
				// 基于Spring 中的发布/订阅机制:发布 EurekaRegistryAvailableEvent
				publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
				// 初始化完成标志
				EurekaServerInitializerConfiguration.this.running = true;
				// 基于Spring 中的发布/订阅机制:发布 EurekaServerStartedEvent
				publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
			}
			catch (Exception ex) {
				// Help!
				log.error("Could not initialize Eureka servlet context", ex);
			}
		}).start();
	}
}

4.1.2 EurekaServerBootstrap.contextInitialized(ServletContext context)

在 EurekaServerBootstrap 这个类的 contextInitialized(ServletContext context) 方法中,会调用 initEurekaServerContext(); 进行初始化,具体代码如下。

public class EurekaServerBootstrap {
	/**
	 * 进行Eureka Server初始化
	 * 1. 初始化 environment
	 * 2. 初始化 上下文
	 */
	public void contextInitialized(ServletContext context) {
		try {
			// 初始化 environment
			initEurekaEnvironment();
			// 初始化 上下文
			initEurekaServerContext();

			context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
		}
		catch (Throwable e) {
			log.error("Cannot bootstrap eureka server :", e);
			throw new RuntimeException("Cannot bootstrap eureka server :", e);
		}
	}
	
	/**
	 * 初始化 上下文
	 */
	protected void initEurekaServerContext() throws Exception {
		// For backward compatibility
		JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
				XStream.PRIORITY_VERY_HIGH);
		XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
				XStream.PRIORITY_VERY_HIGH);

		if (isAws(this.applicationInfoManager.getInfo())) {
			this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
					this.eurekaClientConfig, this.registry, this.applicationInfoManager);
			this.awsBinder.start();
		}

		EurekaServerContextHolder.initialize(this.serverContext);

		log.info("Initialized server context");

		// Copy registry from neighboring eureka node
		// 从相邻的 Eureka 节点复制注册信息
		int registryCount = this.registry.syncUp();
		// 初始化 expectedNumberOfClientsSendingRenews 值
		this.registry.openForTraffic(this.applicationInfoManager, registryCount);

		// Register all monitoring statistics.
		// 注册所有监控统计信息
		EurekaMonitors.registerAllStats();
	}
}

4.1.3 InstanceRegistry.openForTraffic(ApplicationInfoManager applicationInfoManager, int count)

openForTraffic() 方法中,会初始化 expectedNumberOfClientsSendingRenews 的值,该值的含义是: 预期每分钟收到续约的客户端数量,该值的大小取决于注册到 Eureka Server 上的服务数量,所以,只要涉及到服务数量的变更,都需要重新计算 expectedNumberOfClientsSendingRenews 的值。具体代码如下。

public class InstanceRegistry extends PeerAwareInstanceRegistryImpl implements ApplicationContextAware {

	@Override
	public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
		// 调用父类 PeerAwareInstanceRegistryImpl 的 openForTraffic() 方法
		// defaultOpenForTrafficCount:默认为1,可通过 eureka.server.defaultOpenForTrafficCount 进行配置
		super.openForTraffic(applicationInfoManager,
				count == 0 ? this.defaultOpenForTrafficCount : count);
	}
}

PeerAwareInstanceRegistryImpl.openForTraffic(ApplicationInfoManager applicationInfoManager, int count) 方法。

@Singleton
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
	@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; // 初始默认值为1
        
        // 更新每分钟最小的续约数量
        updateRenewsPerMinThreshold();
        
        logger.info("Got {} instances from neighboring DS node", count);
        logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
        this.startupTime = System.currentTimeMillis();
        if (count > 0) {
            this.peerInstancesTransferEmptyOnStartup = false;
        }
        DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
        boolean isAws = Name.Amazon == selfName;
        if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
            logger.info("Priming AWS connections for all replicas..");
            primeAwsReplicas(applicationInfoManager);
        }
        logger.info("Changing status to UP");
        applicationInfoManager.setInstanceStatus(InstanceStatus.UP);

		// 重要方法:初始化后的一些动作,如:开启自我保护机制开启的定时任务
        super.postInit();
    }
    
    /**
     * 更新每分钟最小的续约数量
     */
	protected void updateRenewsPerMinThreshold() {
        this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
                * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
                * serverConfig.getRenewalPercentThreshold());
    }
}

通过对Eureka Server初始化逻辑的分析,可指定自我保护机制阈值计算公式为:

this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
                * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
                * serverConfig.getRenewalPercentThreshold());
  • expectedNumberOfClientsSendingRenews:预期的客户端服务数量。
  • expectedClientRenewalIntervalSeconds:预期的客户端心跳间隔秒数,默认30s。
  • renewalPercentThreshold:自我保护续约百分比阈值因子,默认0.85

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

4.2 触发自动续约阈值重新计算

expectedNumberOfClientsSendingRenewsnumberOfRenewsPerMinThreshold 这两个值,会随着新增服务注册以及服务下线的触发而发生变化。由此可知,自我保护机制阈值计算公式可知,在服务的上线、下线等,只要涉及到服务数量的变化,都会触发阈值的重新计算。Eureka 默认15min 也会定时更新一次阈值。

4.2.1 服务上线

服务上线的核心逻辑在 PeerAwareInstanceRegistryImpl.register(final InstanceInfo info, final boolean isReplication) 方法中,具体逻辑在 Spring Cloud Netflix-Eureka(三)、自我保护机制 中分析过,这里就不过多描述。

PeerAwareInstanceRegistryImpl.register(final InstanceInfo info, final boolean isReplication) 方法中,会调用父类 AbstractInstanceRegistry 中的 register() 方法。

public abstract class AbstractInstanceRegistry implements InstanceRegistry {
	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();
            }
        }
    }
}

4.2.2 服务下线

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

@Singleton
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
	/**
	 * 服务下线
	 */
	@Override
    public boolean cancel(final String appName, final String id,
                          final boolean isReplication) {
        // 调用父类的方法,根据 appName 和 当前实例 id 进行服务下线
        if (super.cancel(appName, id, isReplication)) {
            replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);

            return true;
        }
        return false;
    }
}

PeerAwareInstanceRegistryImpl.cancel(final String appName, final String id, final boolean isReplication) 方法中,会调用父类AbstractInstanceRegistry 的 cancel(final String appName, final String id, final boolean isReplication) 方法。

public abstract class AbstractInstanceRegistry implements InstanceRegistry {
	@Override
    public boolean cancel(String appName, String id, boolean isReplication) {
        return internalCancel(appName, id, isReplication);
    }
    
	protected boolean internalCancel(String appName, String id, boolean isReplication) {
        read.lock();
        try {
            CANCEL.increment(isReplication);
            // 根据 appName 获取服务所有实例
            Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
            Lease<InstanceInfo> leaseToCancel = null;
            if (gMap != null) {
            	// 删除当前id 的实例
                leaseToCancel = gMap.remove(id);
            }
            // 把当前实例放入最近删除的队列中
            recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));

			// 删除当前id 的实例
            InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
            if (instanceStatus != null) {
                logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
            }
            if (leaseToCancel == null) {// 如果被cancel的lease为空,则表示该lease不存在与服务注册中心,直接return
                CANCEL_NOT_FOUND.increment(isReplication);
                logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
                return false;
            } else {
            	// 服务下线
                leaseToCancel.cancel();
                
                InstanceInfo instanceInfo = leaseToCancel.getHolder();
                String vip = null;
                String svip = null;
                if (instanceInfo != null) {
                    instanceInfo.setActionType(ActionType.DELETED);
            		// 把当前实例放入最近更新的队列中
                    recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
                    instanceInfo.setLastUpdatedTimestamp();
                    vip = instanceInfo.getVIPAddress();
                    svip = instanceInfo.getSecureVipAddress();
                }

				// 清楚缓存:保证不会获取到脏数据
                invalidateCache(appName, vip, svip);
                logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
            }
        } finally {
            read.unlock();
        }

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

        return true;
    }
}

4.2.3 每隔15分钟刷新自我保护阈值

为了保证自我保护阈值的正确,Eureka Server 还增加了一个补偿机制,每隔15分钟(默认),更新一次自我保护阈值。

可通过 renewalThresholdUpdateIntervalMs 配置定时更新阈值的默认值。

Eureka Server 在服务启动过程中,会想 Spring 容器注入一个 DefaultEurekaServerContext 实例,具体代码如下。

@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
	@Bean
	@ConditionalOnMissingBean
	public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
			PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
		return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
				registry, peerEurekaNodes, this.applicationInfoManager);
	}
}

DefaultEurekaServerContext 类使用了 Spring 中的 @PostConstruct 注解,实例注入到 IoC 容器后,会调用 @PostConstruct 标注的方法,完成一些初始化动作。

@Singleton
public class DefaultEurekaServerContext implements EurekaServerContext {
	@PostConstruct
    @Override
    public void initialize() {
        logger.info("Initializing ...");
        peerEurekaNodes.start();
        try {
        	// registry 为 PeerAwareInstanceRegistry 实例
            registry.init(peerEurekaNodes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        logger.info("Initialized");
    }
}

PeerAwareInstanceRegistry.init(PeerEurekaNodes peerEurekaNodes) 方法。

@Singleton
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);
        }
    }
    
    /**
     * 开启一个定时刷新阈值的任务
     */
    private void scheduleRenewalThresholdUpdateTask() {
        timer.schedule(new TimerTask() {
                           @Override
                           public void run() {
                           	   // 更新阈值
                               updateRenewalThreshold();
                           }
                       }, serverConfig.getRenewalThresholdUpdateIntervalMs(),// 更新时间间隔
                serverConfig.getRenewalThresholdUpdateIntervalMs());// 更新时间间隔
    }
    
    /**
     * 更新阈值
     */
	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) {
                // Update threshold only if the threshold is greater than the
                // current expected threshold or if self preservation is disabled.
                // 当节点数量count大于最小续约数量时
                // 或者没有开启自我保护机制的情况下,重新计算 expectedNumberOfClientsSendingRenews 和 numberOfRenewsPerMinThreshold
                if ((count) > (serverConfig.getRenewalPercentThreshold() * expectedNumberOfClientsSendingRenews)
                        || (!this.isSelfPreservationModeEnabled())) {
                    // 更新 发送续订的预期客户数量
                    this.expectedNumberOfClientsSendingRenews = count;
                    // 重新计算 自我保护机制阈值
                    updateRenewsPerMinThreshold();
                }
            }
            logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
        } catch (Throwable e) {
            logger.error("Cannot update renewal threshold", e);
        }
    }
}

4.3 自我保护机制的触发

在上面分析的 InstanceRegistry.openForTraffic(ApplicationInfoManager applicationInfoManager, int count) 中,父类 PeerAwareInstanceRegistryImpl 的 openForTraffic() 方法,有一段很重要的逻辑 super.postInit(); ,该方法会完成初始化后的一些动作,比如 开启自我保护机制开启的定时任务。

super.postInit() 方法如下。

@Singleton
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
	@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; // 初始默认值为1
        
        // 更新每分钟最小的续约数量
        updateRenewsPerMinThreshold();
        
        logger.info("Got {} instances from neighboring DS node", count);
        logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
        this.startupTime = System.currentTimeMillis();
        if (count > 0) {
            this.peerInstancesTransferEmptyOnStartup = false;
        }
        DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
        boolean isAws = Name.Amazon == selfName;
        if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
            logger.info("Priming AWS connections for all replicas..");
            primeAwsReplicas(applicationInfoManager);
        }
        logger.info("Changing status to UP");
        applicationInfoManager.setInstanceStatus(InstanceStatus.UP);

		// 重要方法:初始化后的一些动作,如:开启自我保护机制开启的定时任务
        super.postInit();
    }
}
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
	protected void postInit() {
		// 开启一个定时任务,用来实现每分钟的续约数量,每隔 60s 归 0 重新计算
        renewsLastMin.start();
        if (evictionTaskRef.get() != null) {
            evictionTaskRef.get().cancel();
        }
        
        // 启动一个定时任务EvictionTask,每隔60s执行一次
        evictionTaskRef.set(new EvictionTask());
        evictionTimer.schedule(evictionTaskRef.get(),
                serverConfig.getEvictionIntervalTimerInMs(),// 默认60秒
                serverConfig.getEvictionIntervalTimerInMs());// 默认60秒
    }
}

EvictionTask 主要是用来判断是否开启自我保护机制。

4.3.1 EvictionTask

EvictionTask 为 AbstractInstanceRegistry 中的一个内部类,主要是用来判断是否开启自我保护机制 和 客户端心跳时间定期检测。核心代码如下。

public abstract class AbstractInstanceRegistry implements InstanceRegistry {
	class EvictionTask extends TimerTask {
		@Override
        public void run() {
            try {
            	/**
            	 * 获取补偿时间毫秒数
            	 * 补偿时间定义:为自前一次迭代以来执行该任务的实际时间与配置的执行时间。
            	 * 这对于时间的变化(例如,由于时钟倾斜或gc)导致实际的回收任务按照配置的周期比预期的时间执行得晚的情况非常有用。
            	 */
                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 为 AbstractInstanceRegistry 中的方法
	public void evict(long additionalLeaseMs) {
	        logger.debug("Running the evict task");
	        
			// 是否需要开启自我保护机制,如果需要,那么直接RETURE, 不需要继续往下执行了
	        if (!isLeaseExpirationEnabled()) {
	            logger.debug("DS: lease expiration is currently disabled.");
	            return;
	        }
	
			
	        // We collect first all expired items, to evict them in random order. For large eviction sets,
	        // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
	        // the impact should be evenly distributed across all applications.
	        // 循环遍历 registry,获取已经过期的服务,放入expiredLeases列表,进行服务剔除
	        List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
	        for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
	            Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
	            if (leaseMap != null) {
	                for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
	                    Lease<InstanceInfo> lease = leaseEntry.getValue();
	                    // 判断当前服务是否已经过期,默认90s没有收到心跳,则为过期
	                    if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
	                        expiredLeases.add(lease);
	                    }
	                }
	            }
	        }
	
	        // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
	        // triggering self-preservation. Without that we would wipe out full registry.
	        // 获取注册的实例数量
	        int registrySize = (int) getLocalRegistrySize();
	        // 主要是为了避免开启自动保护机制, 所以会逐步过期
	        int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
	        // 可以过期的数量
	        int evictionLimit = registrySize - registrySizeThreshold;
	
			// 取 过期数量和可以过期的数量 最小值
	        int toEvict = Math.min(expiredLeases.size(), evictionLimit);
	        if (toEvict > 0) {// 剔除 expiredLeases列表中所有服务
	            logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);
	
	            Random random = new Random(System.currentTimeMillis());
	            for (int i = 0; i < toEvict; i++) {
	                // Pick a random item (Knuth shuffle algorithm)
	                int next = i + random.nextInt(expiredLeases.size() - i);
	                Collections.swap(expiredLeases, i, next);
	                // 随机取一个过期的节点
	                Lease<InstanceInfo> lease = expiredLeases.get(i);
	
	                String appName = lease.getHolder().getAppName();
	                String id = lease.getHolder().getId();
	                // 增加过期数量
	                EXPIRED.increment();
	                logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
					
					// 服务下线
	                internalCancel(appName, id, false);
	            }
	        }
	    }
}

4.3.2 isLeaseExpirationEnabled()

isLeaseExpirationEnabled() 方法为接口 InstanceRegistry 中的一个方法,用来判断是否需要开启自我保护机制,返回true为开启,false为关闭,由它的实现类进行实现。

PeerAwareInstanceRegistryImpl 类实现了 InstanceRegistry 接口,并重新了 isLeaseExpirationEnabled() 方法。具体代码如下。

@Singleton
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
	@Override
    public boolean isLeaseExpirationEnabled() {
    	// 是否开启了自我保护机制,如果没有,则跳过,默认是开启
        if (!isSelfPreservationModeEnabled()) {
            // The self preservation mode is disabled, hence allowing the instances to expire.
            return true;
        }
        // 计算是否需要开启自我保护,判断最后一分钟收到的续约数量是否大于 numberOfRenewsPerMinThreshold
        return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
    }
}

五、总结

至此,我们就把 Eureka Server 的自我保护机制做了一个详细的分析,Eureka Server 的自我保护机制设计是为了可以很好的应对因网络故障导致部分节点失联的情况,而不会应该网络延迟波动导致服务被误杀的情况,保证了服务的高可用。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值