Eureka自我保护机制源码解析

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

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

/**
* 指望最小每分钟续租次数
*/
protected volatile int numberOfRenewsPerMinThreshold;
/**
* 指望最大每分钟续租次数
*/
protected volatile int expectedNumberOfRenewsPerMin;
服务端初始化

服务端的启动文章能够看这篇文章:EurekaServer自动装配及启动流程解析 在服务端启动、从其余集群同步完信息后执行了一个方法:openForTrafficide

public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
   this.expectedNumberOfRenewsPerMin = count * 2;
   this.numberOfRenewsPerMinThreshold =
           (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
    }

指望每分钟最大续租次数为:当前服务端已经注册的客户端的数量乘2,为啥呢,由于默认Eureka的续约是30秒 指望每分钟最小续租次数为:最大续租次数乘续租百分比,默认续租百分比是0.85,也就是说当某个时间窗内若是存在超过百分之十五的客户端没有再续租的话则开启自我保护模式this

自我保护模式的定时任务

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

@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这个定时任务就是跟自我保护模式相关的了code

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

其中getRenewalThresholdUpdateIntervalMs默认值是15分钟server

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步,第一步就不说了,看第二步 当最大续租数量大于最小续租数量时或者没有开启自我保护模式时能够从新计算两个值,不然不能从新计算get

客户端注册
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,而且最近续约实例数量大于最小期待续租数量

1

相关文章
相关标签/搜索