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