springcloud源码之eureka初始化/服务剔除/自我保护机制

springcloud源码之eureka初始化+服务剔除+自我保护机制

eureka初始化

@Import(EurekaServerInitializerConfiguration.class)
public class EurekaServerAutoConfiguration

EurekaServerAutoConfiguration上面import的类就是做eureka初始化和服务剔除

eureka初始化的入口说明

EurekaServerAutoConfiguration——>
EurekaServerInitializerConfiguration#start()——>
EurekaServerBootstrap#contextInitialized()——>
EurekaServerBootstrap#initEurekaServerContext()
{
	this.registry.syncUp()
}——>
PeerAwareInstanceRegistryImpl#syncUp(){
	//这里可以理解为eureka-server的初始化,即同步集群信息
}

同步集群信息

@Override
	//这段代码debug起来相当麻烦,如果是单机就比较简单,集群情况就很复杂
	//debug情况一:三台server,server1,server2,server3依次开启,syncUp会被依次调用三次
	//在i=0的时候,server1,server2,server3 eurekaClient.getApplications()都拿不到数据
	//然后每个人都等30s进行下次get,才可能拿到,然后都注册进各自的AbstractInstanceRegistry类的一个
	//ConcurrentHashMap,名字是registry,也就是说每个server都有一个AbstractInstanceRegistry实例,
	//每个AbstractInstanceRegistry的registry目前都有这三个server的信息
	
	//debug情况二:
	//三台server,server1,server2,server3,一台client user
	//现在server1和server2和user已经启动,现在启动server3,server3进入
	//syncUpeurekaClient.getApplications();拿到server1,server2,server3,user的信息,注册进自己的
	//registry,也就是说server1,server2,server3各自都有四个微服务实例,其中server1,server2,server3
	//属于一个微服务组,user属于一个微服务组
	
    public int syncUp() {
        int count = 0;
        //getRegistrySyncRetries==5次
        //getRegistrySyncRetryWaitMs=30秒
        //serverConfig在EurekaServerConfigBean类里面,可以通过eureka.server.xxx配置
        for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
        	//除了第一次其他都要休眠30s
            if (i > 0) {
                    Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
            }
            //这行代码极其复杂,他是从一个AutomicReference里拿数据,但是如果你要搞清楚AutomicReference
            //什么时候放的数据就很麻烦,只要记住这行代码可以把集群中所有微服务拿到,不管server还是client
            Applications apps = eurekaClient.getApplications();
            //如果apps 里有微服务
            for (Application app : apps.getRegisteredApplications()) {
            	//遍历所有微服务实例一个个注册进去,isReplacation=true告诉注册的时候不要集群同步
                for (InstanceInfo instance : app.getInstances()) {
                      register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
                      count++;
                }
            }
        }
        return count;
    }

eureka服务剔除

EurekaServerAutoConfiguration——>
EurekaServerInitializerConfiguration#start()——>
EurekaServerBootstrap#contextInitialized()——>
EurekaServerBootstrap#initEurekaServerContext()
{
	this.registry.openForTraffic()
}——>
PeerAwareInstanceRegistryImpl#openForTraffic(){
	this.expectedNumberOfClientsSendingRenews = count;
	//更新每分钟应该收到心跳的阈值,低于该阈值会触发自我保护机制,在我的第一篇博客eureka源码服务注册解释过
    updateRenewsPerMinThreshold();
    super.postInit();
}——>
AbstractInstanceRegistry#postInit(){
	//这里就是服务剔除
	renewsLastMin.start();
        if (evictionTaskRef.get() != null) {
            evictionTaskRef.get().cancel();
        }
        evictionTaskRef.set(new EvictionTask());
        evictionTimer.schedule(evictionTaskRef.get(),
        		//EvictionIntervalTimerInMs每隔多久执行一次剔除,默认60s
                serverConfig.getEvictionIntervalTimerInMs(),
                serverConfig.getEvictionIntervalTimerInMs());
}

EvictionTask
剔除算法如下

public void evict(long additionalLeaseMs) {

        if (!isLeaseExpirationEnabled()) {
        	//进入这里就说明触发了自我保护机制,一旦触发,就不会再剔除实例,直接return
            return;
        }
		//二重for找出所有过期实例放入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();
                    if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
                        expiredLeases.add(lease);
                    }
                }
            }
        }
		
		//共有多少微服务实例,server+client
        int registrySize = (int) getLocalRegistrySize();
        //微服务实例阈值=registrySize *85%(默认)
        int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
        //最多可以剔除多少个微服务
        int evictionLimit = registrySize - registrySizeThreshold;
		//取小
        int toEvict = Math.min(expiredLeases.size(), evictionLimit);
        if (toEvict > 0) {
            Random random = new Random(System.currentTimeMillis());
            //随机剔除toEvict个微服务
            for (int i = 0; i < toEvict; i++) {
                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();
              	//服务下架
                internalCancel(appName, id, false);
            }
        }
    }

eureka自我保护机制

//返回false说明触发了自我保护机制
public boolean isLeaseExpirationEnabled() {
		//如果配置了关闭自我保护机制,这里直接返回true,告诉调用者无需自我保护
        if (!isSelfPreservationModeEnabled()) {
            return true;
        }
        //如果每分钟接收心跳数阈值>0&&最后一分钟接收到心跳数>阈值返回true,告诉调用者无需自我保护
       	//只有当最后一分钟心跳数<阈值才会触发自我保护机制
        return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
    }

总结

1:服务初始化集群同步信息,重试5次,每次间隔30s
2:开启定时器每过60s清理过期的微服务,这里不会清除全部过期的微服务,至多清理15%的微服务,如果大批微服务同一时刻全部宕机,可能触发了自我保护机制则不会清理
3:最后一分钟的心跳数 < 阈值 触发自我保护机制,这个阈值的计算请看服务注册的updateRenewsPerMinThreshold()

发布了164 篇原创文章 · 获赞 81 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/LiuRenyou/article/details/104901206