之前我說明了Eureka注冊中心的保護模式,由於在該模式下不能剔除失效節點,故按原有配置在實際中不剔除總感覺不是太好,所以深入研究了一下。當然,這里重申一下,不管實例是否有效剔除,消費端實現Ribbon重試機制也是必須的。
說下背景,在微服務架構中,有個CAP原則(一致性,可用性,分區容錯性),三者由於存在互斥,只能同時滿足其二,第三點需要有一定舍棄。Eureka舍棄了強一致性,所以在進入保護模式后,失效節點的一致性不能得到保證。
以下是我驗證后的幾種方式,可以實現服務的及時剔除。
1、關閉自我保護模式eureka.server.enable-self-preservation=false來關閉。但是,這種方式有違eureka的CAP原則,所以,我並不推薦這種方式。
2、在說明之前,我們先看下源碼。
以下代碼判斷是否進入刷新服務列表的步驟。該代碼在AbstractInstanceRegistry.java和PeerAwareInstanceRegistryImpl.java中。
public void evict(long additionalLeaseMs) { logger.debug("Running the evict task"); //主要是看isLeaseExpirationEnabled返回值 if (!isLeaseExpirationEnabled()) { logger.debug("DS: lease expiration is currently disabled."); return; } //進入篩選過期實例的方法。這里省略 }
public boolean isLeaseExpirationEnabled() { //isSelfPreservationModeEnabled由是否開啟保護模式配置決定,默認為true if (!isSelfPreservationModeEnabled()) { return true; } return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold; }
主要看numberOfRenewsPerMinThreshold和getNumOfRenewsInLastMin()方法。我們主要看numberOfRenewsPerMinThreshold初始化的地方。getNumOfRenewsInLastMin()方法是統計最后一分鍾內的心跳統計總數。
//PeerAwareInstanceRegistryImpl.java public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) { // Renewals happen every 30 seconds and for a minute it should be a factor of 2. this.expectedNumberOfRenewsPerMin = count * 2; this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()); //.... }
count是注冊在注冊中心的實例總數,包括高可用的另外注冊中心。我們可以看到,這里是以硬編碼的方式初始化expectedNumberOfRenewsPerMin,通過 實例數*2。為什么*2,因為實例默認發送心跳時間是30s,所以通過*2統計一分鍾類應收到的總的心跳次數,因為是硬編碼,所以不建議修改實例端心跳周期時間!。numberOfRenewsPerMinThreshold是通過總的心跳數乘以允許失敗的比例。默認為0.85。expectedNumberOfRenewsPerMin在默認情況下,會每隔15分鍾刷新一次。
通過上面分析,我們大致了解了不通過關閉自我保護模式下觸發服務剔除操作的條件。得出以下兩種方案來實現失效節點的剔除(親測有效)。
- 通過修改每分鍾心跳成功最低比例來控制注冊中心不進入自我保護模式,實時剔除節點(需要等到實例定義的過期時間,且注冊中心觸發刷新周期)。eureka.server.renewal-percent-threshold來配置比例。舉個例子,當前我們有4個服務實例,計算所知,每分鍾有4*2=8個心跳,當需要實現一個實例意外掉線后,則每分鍾實際收到心跳為6個,8*x<6時不會進入保護模式,則x<0.75,配置為0.75以下大致可以實現剔除操作,但是由於網絡不穩定因素,存在心跳異常,所以該值盡量設置小於0.75多點,如0.5。為什么默認會0.85呢,我覺得應該是有算法支撐,但從結果來看,保護模式還是適用於大規模的服務集群,當一台或幾台掛掉之后,也不會進入保護模式,默認比例下,可以實現實例的剔除。如果集群較小,如2台,掛掉一台就會進入保護模式,且保護模式意義不大,可以關閉自我保護模式。
- 通過設置實例的心跳時間,改為較小的周期。由於需要判斷每分鍾實際心跳數>實例總數*2*0.85,才不會進入自我保護模式,所以設置心跳周期較小,使實際心跳數多於比例下的心跳數,可以實現實例的及時剔除。實例的心跳周期設置:eureka.instance.lease-renewal-interval-in-seconds,如設置為5等等,具體可根據以上算法來計算合適的值。