前言
以下圖片來(lái)自Netflix官方,圖中顯示Eureka Client會(huì)發(fā)起Renew向注冊(cè)中心做周期性續(xù)約,這樣其他Eureka client通過(guò)Get Registry請(qǐng)求就能獲取到新注冊(cè)應(yīng)用的相關(guān)信息:

1、什么是自我保護(hù)機(jī)制
默認(rèn)情況下,如果Eureka Server在一定時(shí)間內(nèi)(默認(rèn) 90 秒,其實(shí)不止 90 秒)沒(méi)有接收到某個(gè)微服務(wù)實(shí)例的心跳,Eureka Server將會(huì)移除該實(shí)例。但是當(dāng)網(wǎng)絡(luò)分區(qū)故障發(fā)生時(shí),微服務(wù)與Eureka Server之間無(wú)法正常通信,而微服務(wù)本身是正常運(yùn)行的,此時(shí)不應(yīng)該移除這個(gè)微服務(wù),所以引入了自我保護(hù)機(jī)制。
官方對(duì)于自我保護(hù)機(jī)制的定義:
自我保護(hù)模式正是一種針對(duì)網(wǎng)絡(luò)異常波動(dòng)的安全保護(hù)措施,使用自我保護(hù)模式能使 Eureka 集群更加的健壯、穩(wěn)定的運(yùn)行。
自我保護(hù)機(jī)制的工作機(jī)制是:如果在15分鐘內(nèi)超過(guò) 85% 的客戶(hù)端節(jié)點(diǎn)都沒(méi)有正常的心跳,那么Eureka就認(rèn)為客戶(hù)端與注冊(cè)中心出現(xiàn)了網(wǎng)絡(luò)故障,Eureka Server 自動(dòng)進(jìn)入自我保護(hù)機(jī)制,此時(shí)會(huì)出現(xiàn)以下幾種情況:
- 1、Eureka Server不再?gòu)淖?cè)列表中移除因?yàn)殚L(zhǎng)時(shí)間沒(méi)收到心跳而應(yīng)該過(guò)期的服務(wù)。
- 2、Eureka Server仍然能夠接受新服務(wù)的注冊(cè)和查詢(xún)請(qǐng)求,但是不會(huì)被同步到其它節(jié)點(diǎn)上,保證當(dāng)前節(jié)點(diǎn)依然可用。
- 3、當(dāng)網(wǎng)絡(luò)穩(wěn)定時(shí),當(dāng)前Eureka Server新的注冊(cè)信息會(huì)被同步到其它節(jié)點(diǎn)中。
因此Eureka Server可以很好的應(yīng)對(duì)因網(wǎng)絡(luò)故障導(dǎo)致部分節(jié)點(diǎn)失聯(lián)的情況,而不會(huì)像 ZK 那樣如果有一半不可用的情況會(huì)導(dǎo)致整個(gè)集群不可用而變成癱瘓。
Eureka Server 自我保護(hù)機(jī)制,可以通過(guò)通過(guò)配置 eureka.server.enable-self-preservation 來(lái) true 打開(kāi)/ false 禁用 自我保護(hù)機(jī)制,默認(rèn)打開(kāi)狀態(tài),建議生產(chǎn)環(huán)境打開(kāi)此配置。
2、Eureka Server 自我保護(hù)機(jī)制
Eureka Server 服務(wù)過(guò)期 當(dāng)中,在進(jìn)行服務(wù)過(guò)期的時(shí)候,首先會(huì)判斷 Eureka Server 是否開(kāi)啟了自我保護(hù)機(jī)制。
AbstractInstanceRegistry#evict(long)
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
return;
}
//省略剩余代碼。。。。。。
}
}
如果開(kāi)啟了自我保護(hù)機(jī)制也就是 isLeaseExpirationEnabled() 方法返回了 false,就直接返回,不進(jìn)行服務(wù)下線。
PeerAwareInstanceRegistryImpl#isLeaseExpirationEnabled
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
protected volatile int numberOfRenewsPerMinThreshold;
@Override
public boolean isLeaseExpirationEnabled() {
if (!isSelfPreservationModeEnabled()) {
// The self preservation mode is disabled, hence allowing the instances to expire.
return true;
}
return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}
}
首先判斷 Eureka Server 是否開(kāi)啟了自我保護(hù)機(jī)制 eureka.enableSelfPreservation 為 true 開(kāi)啟反之不開(kāi)啟。如果沒(méi)有開(kāi)啟直接返回 true,可以進(jìn)行服務(wù)過(guò)期處理。
然后判斷每分鐘期望的續(xù)約數(shù)(numberOfRenewsPerMinThreshold) 大于 0 并且實(shí)際每分鐘的續(xù)約數(shù)(getNumOfRenewsInLastMin()) 大于每分鐘期望的續(xù)約數(shù)(numberOfRenewsPerMinThreshold)
3、每分鐘應(yīng)用的續(xù)約數(shù)
在 Eureka Server 啟動(dòng)的時(shí)候,在注冊(cè)服務(wù)(AbstractInstanceRegistry)中會(huì)啟動(dòng)一個(gè)定時(shí)任務(wù) MeasuredRate 來(lái)計(jì)算每分鐘應(yīng)用續(xù)約的個(gè)數(shù)。時(shí)序圖如下:

MeasuredRate,它是一個(gè)統(tǒng)計(jì)定時(shí)任務(wù),在 AbstractInstanceRegistry 的構(gòu)建器創(chuàng)建 MeasuredRate 對(duì)象的時(shí)候傳入 1000 * 60 * 1,然后在這里調(diào)用它的 start 方法里面有一個(gè)定時(shí)任務(wù),每隔 60 秒也就是每隔 1 分鐘執(zhí)行一次。這個(gè)定時(shí)任務(wù)里面有 2 個(gè) AtomicLong 類(lèi)型的參數(shù)。一個(gè)是 AtomicLong currentBucket每進(jìn)行一次續(xù)約的時(shí)候就會(huì)調(diào)用它 + 1,另一個(gè)是 AtomicLong lastBucket。
當(dāng) MeasuredRate 任務(wù)每分鐘進(jìn)行執(zhí)行的時(shí)候就會(huì)把 AtomicLong currentBucket 里面的值設(shè)置到 AtomicLong lastBucket當(dāng)中去,然后把 AtomicLong currentBucket值清空再次計(jì)算。然后通過(guò)獲取 AtomicLong lastBucket 的值就能夠得到最近一分鐘續(xù)約的次數(shù)。
這個(gè)設(shè)計(jì)還是蠻精巧的。
4、每分鐘期望的續(xù)約數(shù)
每分鐘期望的續(xù)約數(shù)是 AbstractInstanceRegistry#numberOfRenewsPerMinThreshold ,這個(gè)值是動(dòng)態(tài)變化的。它提供了AbstractInstanceRegistry#updateRenewsPerMinThreshold 來(lái)動(dòng)態(tài)的更新這個(gè)值。
AbstractInstanceRegistry#updateRenewsPerMinThreshold
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
protected volatile int numberOfRenewsPerMinThreshold;
protected volatile int expectedNumberOfClientsSendingRenews;
protected void updateRenewsPerMinThreshold() {
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
* serverConfig.getRenewalPercentThreshold());
}
}
每分鐘期望的續(xù)約數(shù)是根據(jù) expectedNumberOfClientsSendingRenews (期望 Eureka Client 發(fā)送的續(xù)約數(shù),這個(gè)值會(huì)根據(jù)服務(wù)的動(dòng)作進(jìn)行更新:服務(wù)注冊(cè) + 1與服務(wù)下線 - 1) 來(lái)進(jìn)行判斷的。上面的公式如下:
每分鐘期望的續(xù)約數(shù) = 期望Eureka Client發(fā)送的續(xù)約數(shù) * (60 秒 / 預(yù)計(jì)客戶(hù)端間隔秒數(shù)續(xù)約[默認(rèn) 30 秒]) * 0.85
比如:現(xiàn)在注冊(cè)中心有 20 個(gè)服務(wù)
那么:每分鐘期望的續(xù)約數(shù) = 20 * (60 / 30) * 0.85 = 17
AbstractInstanceRegistry#register
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
//省略其余代碼。。。。。。
// 租約不存在,因此是新的注冊(cè)
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// Since the client wants to register it, increase the number of clients sending renews
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
updateRenewsPerMinThreshold();
}
}
//省略其余代碼。。。。。。
}
}
服務(wù)注冊(cè), expectedNumberOfClientsSendingRenews(期望Eureka Client發(fā)送的續(xù)約數(shù)) + 1,并且更新每分鐘期望的續(xù)約數(shù)。
AbstractInstanceRegistry#internalCancel
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
protected boolean internalCancel(String appName, String id, boolean isReplication) {
//省略上面代碼。。。。。。
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// Since the client wants to cancel it, reduce the number of clients to send renews.
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
updateRenewsPerMinThreshold();
}
}
return true;
}
}
服務(wù)下線, expectedNumberOfClientsSendingRenews(期望Eureka Client發(fā)送的續(xù)約數(shù)) - 1,并且更新每分鐘期望的續(xù)約數(shù)。
參考:
https://blog.csdn.net/u012410733/article/details/112303048