解讀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)文章
Mybatis-plus多數(shù)據(jù)源配置的兩種方式總結(jié)
這篇文章主要為大家詳細介紹了Mybatis-plus中多數(shù)據(jù)源配置的兩種方式,文中的示例代碼簡潔易懂,感興趣的小伙伴可以跟隨小編一起了解一下2022-10-10
mybatis的mapper特殊字符轉(zhuǎn)移及動態(tài)SQL條件查詢小結(jié)
mybatis mapper文件中條件查詢符,如>=,<,之類是不能直接寫的會報錯的需要轉(zhuǎn)移一下,本文給大家介紹了常見的條件查詢操作,對mybatis的mapper特殊字符及動態(tài)SQL條件查詢相關(guān)知識感興趣的朋友一起看看吧2021-09-09
解決Eclipse發(fā)布到Tomcat丟失依賴jar包的問題
這篇文章介紹了如何在Eclipse中配置部署裝配功能,以確保在將Web項目發(fā)布到Tomcat服務(wù)器時不會丟失任何依賴jar包,通過手動配置或使用構(gòu)建工具腳本,可以自動化這個過程,提高開發(fā)效率和應(yīng)用程序的穩(wěn)定性,感興趣的朋友跟隨小編一起看看吧2025-01-01
SpringBoot Maven打包失敗報:class lombok.javac.apt.Lombo
最新項目部署的時候,出現(xiàn)了一個maven打包失敗的問題,報:class lombok.javac.apt.LombokProcessor錯誤,所以本文給大家介紹了如何解決SpringBoot Maven 打包失?。篶lass lombok.javac.apt.LombokProcessor 錯誤,需要的朋友可以參考下2023-12-12
spring?cloud之eureka高可用集群和服務(wù)分區(qū)解析
這篇文章主要介紹了spring?cloud之eureka高可用集群和服務(wù)分區(qū)解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03

