Eureka自我保護機制源碼解析

默認狀況下,當EurekaServer在必定時間內(默認90秒)沒有接收到某個客戶端實例的心跳,EurekaServer將會註銷該實例。可是當網絡分區故障發生時,客戶端與EurekaServer之間沒法正常通訊,此時不該該註銷客戶端。Eureka經過「自我保護機制」來解決這個問題:當EurekaServer短期內丟失過多客戶端時,這個節點就會進入自我保護模式。在自我保護模式下,EurekaServer不會剔除任何客戶端。當網絡故障恢復後,該節點會自動退出自我保護模式

自我保護機制的實現是基於維護服務註冊表的類AbstractInstanceRegistry中的2個變量來維護的網絡

/**
* 指望最小每分鐘續租次數
*/
protected volatile int numberOfRenewsPerMinThreshold;
/**
* 指望最大每分鐘續租次數
*/
protected volatile int expectedNumberOfRenewsPerMin;
服務端初始化

服務端的啓動文章能夠看這篇文章:EurekaServer自動裝配及啓動流程解析
在服務端啓動、從其餘集羣同步完信息後執行了一個方法:openForTrafficapp

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

指望每分鐘最大續租次數爲:當前服務端已經註冊的客戶端的數量乘2,爲啥呢,由於默認Eureka的續約是30秒
指望每分鐘最小續租次數爲:最大續租次數乘續租百分比,默認續租百分比是0.85,也就是說當某個時間窗內若是存在超過百分之十五的客戶端沒有再續租的話則開啓自我保護模式ide

自我保護模式的定時任務

DefaultEurekaServerContext類中有一個initialize方法,這個方法在執行過程當中會啓動一個定時任務this

@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這個定時任務就是跟自我保護模式相關的了spa

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

其中getRenewalThresholdUpdateIntervalMs默認值是15分鐘debug

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步,第一步就不說了,看第二步
當最大續租數量大於最小續租數量時或者沒有開啓自我保護模式時能夠從新計算兩個值,不然不能從新計算code

客戶端註冊
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次server

客戶端下線
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());
        }
   }
}

於註冊的處理邏輯剛好相反rem

開啓自我保護模式

以前在Eureka客戶端續約及服務端過時租約清理源碼解析一文的租約過時清理解析過程當中省略了關於自我保護模式的判斷,如今再看一下。這個判斷在租約過時處理方法的開頭:get

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

相關文章
相關標籤/搜索