解讀Eureka的TimedSupervisorTask類(自動調(diào)節(jié)間隔的周期性任務(wù))
起因
一個基于Spring Cloud框架的應(yīng)用,如果注冊到了Eureka server,那么它就會定時更新服務(wù)列表,
這個定時任務(wù)啟動的代碼在com.netflix.discovery.DiscoveryClient類的initScheduledTasks方法中,
如下(來自工程eureka-client,版本1.7.0):
private void initScheduledTasks() {
//更新服務(wù)列表
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
...
//略去其他代碼上述代碼中,scheduler是ScheduledExecutorService接口的實現(xiàn),其schedule方法的官方文檔如下所示:

上圖紅框顯示:
該方法創(chuàng)建的是一次性任務(wù),但是在實際測試中,如果在CacheRefreshThread類的run方法中打個斷點,就會發(fā)現(xiàn)該方法會被周期性調(diào)用;
因此問題就來了:
方法schedule(Callable callable,long delay,TimeUnit unit)創(chuàng)建的明明是個一次性任務(wù),但CacheRefreshThread被周期性執(zhí)行了;
尋找答案
打開的run方法源碼,請注意下面的中文注釋:
public void run() {
Future future = null;
try {
//使用Future,可以設(shè)定子線程的超時時間,這樣當(dāng)前線程就不用無限等待了
future = executor.submit(task);
threadPoolLevelGauge.set((long) executor.getActiveCount());
//指定等待子線程的最長時間
future.get(timeoutMillis, TimeUnit.MILLISECONDS); // block until done or timeout
//delay是個很有用的變量,后面會用到,這里記得每次執(zhí)行任務(wù)成功都會將delay重置
delay.set(timeoutMillis);
threadPoolLevelGauge.set((long) executor.getActiveCount());
} catch (TimeoutException e) {
logger.error("task supervisor timed out", e);
timeoutCounter.increment();
long currentDelay = delay.get();
//任務(wù)線程超時的時候,就把delay變量翻倍,但不會超過外部調(diào)用時設(shè)定的最大延時時間
long newDelay = Math.min(maxDelay, currentDelay * 2);
//設(shè)置為最新的值,考慮到多線程,所以用了CAS
delay.compareAndSet(currentDelay, newDelay);
} catch (RejectedExecutionException e) {
//一旦線程池的阻塞隊列中放滿了待處理任務(wù),觸發(fā)了拒絕策略,就會將調(diào)度器停掉
if (executor.isShutdown() || scheduler.isShutdown()) {
logger.warn("task supervisor shutting down, reject the task", e);
} else {
logger.error("task supervisor rejected the task", e);
}
rejectedCounter.increment();
} catch (Throwable e) {
//一旦出現(xiàn)未知的異常,就停掉調(diào)度器
if (executor.isShutdown() || scheduler.isShutdown()) {
logger.warn("task supervisor shutting down, can't accept the task");
} else {
logger.error("task supervisor threw an exception", e);
}
throwableCounter.increment();
} finally {
//這里任務(wù)要么執(zhí)行完畢,要么發(fā)生異常,都用cancel方法來清理任務(wù);
if (future != null) {
future.cancel(true);
}
//只要調(diào)度器沒有停止,就再指定等待時間之后在執(zhí)行一次同樣的任務(wù)
if (!scheduler.isShutdown()) {
//這里就是周期性任務(wù)的原因:只要沒有停止調(diào)度器,就再創(chuàng)建一次性任務(wù),執(zhí)行時間時dealy的值,
//假設(shè)外部調(diào)用時傳入的超時時間為30秒(構(gòu)造方法的入?yún)imeout),最大間隔時間為50秒(構(gòu)造方法的入?yún)xpBackOffBound)
//如果最近一次任務(wù)沒有超時,那么就在30秒后開始新任務(wù),
//如果最近一次任務(wù)超時了,那么就在50秒后開始新任務(wù)(異常處理中有個乘以二的操作,乘以二后的60秒超過了最大間隔50秒)
scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
}
}
}真相就在上面的最后一行代碼中:
scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS)
執(zhí)行完任務(wù)后,會再次調(diào)用schedule方法,在指定的時間之后執(zhí)行一次相同的任務(wù),這個間隔時間和最近一次任務(wù)是否超時有關(guān),如果超時了就間隔時間就會變大;
總結(jié)
從整體上看,TimedSupervisorTask是固定間隔的周期性任務(wù),一旦遇到超時就會將下一個周期的間隔時間調(diào)大,如果連續(xù)超時,那么每次間隔時間都會增大一倍,一直到達外部參數(shù)設(shè)定的上限為止,一旦新任務(wù)不再超時,間隔時間又會自動恢復(fù)為初始值,另外還有CAS來控制多線程同步,簡潔的代碼,巧妙的設(shè)計,值得我們學(xué)習(xí);
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
idea中@override報錯在實現(xiàn)接口方法時無法使用的解決
文章介紹了在使用IDEA新建Filter類時遇到的@Override注解報錯問題,原因是JDK版本過低,通過在maven配置文件中設(shè)置編譯插件或更改項目JDK版本,解決了該問題2025-12-12
java中判斷字段真實長度的實例(中文2個字符,英文1個字符)
下面小編就為大家?guī)硪黄猨ava中判斷字段真實長度的實例(中文2個字符,英文1個字符)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-01-01
java Arrays快速打印數(shù)組的數(shù)據(jù)元素列表案例
這篇文章主要介紹了java Arrays快速打印數(shù)組的數(shù)據(jù)元素列表案例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Java接口自動化測試框架設(shè)計之Get請求方法和測試詳解
這篇文章主要介紹了Java接口自動化測試框架設(shè)計 Get請求方法和測試,框架設(shè)計我們只是介紹基本的組件,而且框架設(shè)計沒有想象那么難,一步一步跟著做就會了。這篇我們來演示,如果通過Java代碼來實現(xiàn)一個用純代碼實現(xiàn)Http中的Get請求過程,需要的朋友可以參考下2019-07-07
基于Spring AI+MySQL實現(xiàn)文件內(nèi)容相似度的簡單檢測
在做一個 AI 求職的比賽項目中,遇到了一個有趣的需求:用戶上傳自己的簡歷文件后,系統(tǒng)需要計算并返回該簡歷與其他用戶簡歷之間的相似度,想要實現(xiàn)這樣的功能,所以本文給大家分享了如何基于Spring AI+MySQL實現(xiàn)文件內(nèi)容相似度的簡單檢測,需要的朋友可以參考下2025-11-11

