Spring中的Eureka服務(wù)過期詳細(xì)解析
Eureka 過期
如果有一些服務(wù)過期了,或者宕機(jī)了,就不會(huì)調(diào)用shutdown()方法,也不會(huì)去發(fā)送請(qǐng)求下線服務(wù)實(shí)例。
eureka就專門實(shí)現(xiàn)了一套過期的策略,去下線一些過期的服務(wù)。
它的入口就是在eureka server在啟動(dòng)初始化的時(shí)候,registry.openForTraffic(applicationInfoManager, registryCount);。

于是就點(diǎn)進(jìn)方法,下面為它的代碼
@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// Renewals happen every 30 seconds and for a minute it should be a factor of 2.
this.expectedNumberOfClientsSendingRenews = count;
updateRenewsPerMinThreshold();
logger.info("Got {} instances from neighboring DS node", count);
logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
this.startupTime = System.currentTimeMillis();
if (count > 0) {
this.peerInstancesTransferEmptyOnStartup = false;
}
DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
boolean isAws = Name.Amazon == selfName;
if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
logger.info("Priming AWS connections for all replicas..");
primeAwsReplicas(applicationInfoManager);
}
logger.info("Changing status to UP");
applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
super.postInit();
}expectedNumberOfClientsSendingRenews為從注冊(cè)表獲取的注冊(cè)服務(wù)實(shí)例的數(shù)量。
protected void updateRenewsPerMinThreshold() {
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
* serverConfig.getRenewalPercentThreshold());
}getExpectedClientRenewalIntervalSeconds 默認(rèn)為30秒,getRenewalPercentThreshold 是檢測(cè)的一個(gè)系數(shù)為85%,這個(gè)公式會(huì)算出期望的存活的數(shù)量的。如果注冊(cè)的數(shù)量為20個(gè)* 2 * 0.85 =34,這個(gè)是在自我保護(hù)機(jī)制中進(jìn)行對(duì)比的,判斷是否進(jìn)入保護(hù)機(jī)制,進(jìn)入的話,則不能摘除服務(wù),不會(huì)走下面的方法 。下面一段代碼都不是太重要,當(dāng)來到super.postInit();的時(shí)候,顧名思義,是要做一些初始化的事情。下面為詳細(xì)代碼:
protected void postInit() {
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
evictionTaskRef.set(new EvictionTask());
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}renewsLastMin.start();看上去是要開啟一個(gè)線程,看名字是續(xù)租最后一分鐘,代碼設(shè)計(jì)的還是很巧妙的,設(shè)置出2個(gè)Bucket,一個(gè)專門存當(dāng)前時(shí)間,一個(gè)存一分鐘之前的時(shí)間,這個(gè)線程1分鐘運(yùn)行一次,這樣就可以拿到一分鐘之前的時(shí)間了。
private final long sampleInterval; //1分鐘
public synchronized void start() {
if (!isActive) {
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
// Zero out the current bucket.
//每分鐘這塊調(diào)度異常
//currentBucket 是用來更新當(dāng)前這一分鐘的次數(shù)的
//lastBucket 是保留了上一分鐘的心跳次數(shù)
//timer調(diào)度任務(wù),1分鐘來一次,就將上一分鐘的心跳次數(shù)設(shè)置到lastBucket中去
lastBucket.set(currentBucket.getAndSet(0));
} catch (Throwable e) {
logger.error("Cannot reset the Measured Rate", e);
}
}
}, sampleInterval, sampleInterval);
isActive = true;
}
}evictionTaskRef.set(new EvictionTask());這里會(huì)new一個(gè)task,下面就看一下,它到底做了什么,compensation的意思是補(bǔ)償,那這個(gè)compensationTimeMs就是補(bǔ)償時(shí)間(毫秒)。在getCompensationTimeMs這個(gè)方法中,會(huì)用當(dāng)前時(shí)間減去上一次執(zhí)行的時(shí)間,在減去摘除間隔時(shí)間(60s),在判斷這個(gè)時(shí)間是否大于0,如果大于0,這說明延遲了,則會(huì)得到一個(gè)補(bǔ)償時(shí)間,這個(gè)時(shí)間很關(guān)鍵,因?yàn)楹竺嫠惴?wù)實(shí)例是否過期要下線的時(shí)候,也和它有關(guān)系。
/* visible for testing */ class EvictionTask extends TimerTask {
private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l);
@Override
public void run() {
try {
long compensationTimeMs = getCompensationTimeMs();
logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
evict(compensationTimeMs);
} catch (Throwable e) {
logger.error("Could not run the evict task", e);
}
}
/**
* compute a compensation time defined as the actual time this task was executed since the prev iteration,
* vs the configured amount of time for execution. This is useful for cases where changes in time (due to
* clock skew or gc for example) causes the actual eviction task to execute later than the desired time
* according to the configured cycle.
*/
//比預(yù)期的時(shí)間晚
long getCompensationTimeMs() {
//先獲取當(dāng)前時(shí)間
long currNanos = getCurrentTimeNano();
//上一次這個(gè)EvictionTask被執(zhí)行的時(shí)間。
long lastNanos = lastExecutionNanosRef.getAndSet(currNanos);
if (lastNanos == 0l) {
return 0l;
}
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);
long compensationTime = elapsedMs - serverConfig.getEvictionIntervalTimerInMs();
return compensationTime <= 0l ? 0l : compensationTime;
}
long getCurrentTimeNano() { // for testing
return System.nanoTime();
}
}接著就把補(bǔ)償時(shí)間帶到摘除的方法中,代碼如下:
public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
//是否運(yùn)行主動(dòng)刪除故障的實(shí)例,自我保護(hù)機(jī)制有關(guān)
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
return;
}
// We collect first all expired items, to evict them in random order. For large eviction sets,
// if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
// the impact should be evenly distributed across all applications.
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();
//如果超過3分鐘加上補(bǔ)償時(shí)間,就認(rèn)為故障了
if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
expiredLeases.add(lease);
}
}
}
}
// To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
// triggering self-preservation. Without that we would wipe out full registry.
//20
int registrySize = (int) getLocalRegistrySize();
// 20*0.85
int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
//3
int evictionLimit = registrySize - registrySizeThreshold;
int toEvict = Math.min(expiredLeases.size(), evictionLimit);
if (toEvict > 0) {
logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);
Random random = new Random(System.currentTimeMillis());
//隨機(jī)挑選3個(gè)服務(wù)
for (int i = 0; i < toEvict; i++) {
// Pick a random item (Knuth shuffle algorithm)
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();
EXPIRED.increment();
logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
//對(duì)這個(gè)隨機(jī)挑選出來的,這個(gè)也是shutdown方法調(diào)用的
internalCancel(appName, id, false);
}
}
}isLeaseExpirationEnabled()這個(gè)方法,主要就是自我保護(hù),前面已經(jīng)說過了,此處就不在介紹了。
接著會(huì)遍歷拿到所有的服務(wù)實(shí)例,里面包含它的續(xù)租時(shí)間,會(huì)判斷它有沒有過期,過期的話就會(huì)加到要摘除的集合中。公式就是當(dāng)前時(shí)間是否大約最后一次修改的時(shí)候加上續(xù)租時(shí)間和補(bǔ)償時(shí)間,大于的話就是過期了。
public boolean isExpired(long additionalLeaseMs) {
//當(dāng)前時(shí)間是否大于上一次心跳時(shí)間加上90s+再加上補(bǔ)償時(shí)間
return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));
}這塊代碼中,會(huì)計(jì)算出一個(gè)最大的摘除的個(gè)數(shù),如果是20個(gè)的話,最大摘除3個(gè)。因此最后會(huì)選擇這3和故障的6個(gè)選擇一個(gè)最小的,進(jìn)行隨機(jī)摘除,然后調(diào)用下線的方法。
到此這篇關(guān)于Spring中的Eureka服務(wù)過期詳細(xì)解析的文章就介紹到這了,更多相關(guān)Eureka服務(wù)過期內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringCloud啟動(dòng)eureka server后,沒報(bào)錯(cuò)卻不能訪問管理頁面(404問題)
- 解決Error:(5,55)java:程序包org.springframework.cloud.netflix.eureka.server不存在問題
- SpringCloud集成Eureka并實(shí)現(xiàn)負(fù)載均衡的過程詳解
- SpringCloud中的Eureka注冊(cè)中心詳細(xì)解讀
- springboot?去掉netflix?禁用Eureka的解決方法
- SpringCloud中的Eureka集群配置方法
- Spring Cloud Nacos 和 Eureka區(qū)別解析
- Spring?Cloud?Eureka高可用配置(踩坑記錄)
相關(guān)文章
Java Swing SpringLayout彈性布局的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java Swing SpringLayout彈性布局的實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
DynamicDataSource怎樣解決多數(shù)據(jù)源的事務(wù)問題
這篇文章主要介紹了DynamicDataSource怎樣解決多數(shù)據(jù)源的事務(wù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
使用Java和Selenium實(shí)現(xiàn)滑塊驗(yàn)證的自動(dòng)化登錄功能
在現(xiàn)代Web應(yīng)用中,滑塊驗(yàn)證碼被廣泛用于防止自動(dòng)化腳本的濫用,滑塊驗(yàn)證通常要求用戶通過拖動(dòng)滑塊來完成驗(yàn)證,然而,在某些場(chǎng)景下,如自動(dòng)化測(cè)試或批量登錄,我們需要通過編程手段解決滑塊驗(yàn)證問題,本文將詳細(xì)介紹如何使用Java和Selenium實(shí)現(xiàn)滑塊驗(yàn)證的自動(dòng)化登錄2025-01-01
springAI結(jié)合ollama簡(jiǎn)單實(shí)現(xiàn)小結(jié)
本文主要介紹了springAI結(jié)合ollama簡(jiǎn)單實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03
Java線程實(shí)現(xiàn)的三種方式詳細(xì)解析
這篇文章主要介紹了Java線程實(shí)現(xiàn)的三種方式詳細(xì)解析,Java多線程實(shí)現(xiàn)方式主要有三種,繼承Thread類、實(shí)現(xiàn)Runnable接口、使用ExecutorService、Callable、Future實(shí)現(xiàn)有返回結(jié)果的多線程,需要的朋友可以參考下2023-12-12
Java統(tǒng)計(jì)50個(gè)10到50之間整數(shù)的隨機(jī)出現(xiàn)次數(shù)
這篇文章主要為大家詳細(xì)介紹了Java統(tǒng)計(jì)50個(gè)10到50之間整數(shù)的隨機(jī)出現(xiàn)次數(shù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
java基于TCP協(xié)議實(shí)現(xiàn)聊天程序
這篇文章主要為大家詳細(xì)介紹了java基于TCP協(xié)議實(shí)現(xiàn)聊天程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
C#使用MySQLConnectorNet和MySQLDriverCS操作MySQL的方法
這篇文章主要介紹了C#使用MySQLConnectorNet和MySQLDriverCS操作MySQL的方法,相比普通方法能夠在Windows下簡(jiǎn)化很多操作步驟,需要的朋友可以參考下2016-04-04

