Spring 定時(shí)任務(wù)簡(jiǎn)介
Cloud Native
定時(shí)任務(wù)是業(yè)務(wù)應(yīng)用開發(fā)中非常普遍存在的場(chǎng)景(如:每分鐘掃描超時(shí)支付的訂單,每小時(shí)清理一次數(shù)據(jù)庫(kù)歷史數(shù)據(jù),每天統(tǒng)計(jì)前一天的數(shù)據(jù)并生成報(bào)表等等),解決方案很多,Spring 框架提供了一種通過(guò)注解來(lái)配置定時(shí)任務(wù)的解決方案,接入非常的簡(jiǎn)單,僅需如下兩步:
1. 在啟動(dòng)類上添加注解@EnableScheduling
@SpringBootApplication @EnableScheduling // 添加定時(shí)任務(wù)啟動(dòng)注解 public class SpringSchedulerApplication { public static void main(String[] args) { SpringApplication.run(SpringSchedulerApplication.class, args); } }2. 開發(fā)定時(shí)任務(wù) Bean 并配置相應(yīng)的定時(shí)注解@Scheduled
@Component public class SpringScheduledProcessor { /** * 通過(guò)Cron表達(dá)式指定頻率或指定時(shí)間 */ @Scheduled(cron = "0/5 * * * * ?") public void doSomethingByCron() { System.out.println("do something"); } /** * 固定執(zhí)行間隔時(shí)間 */ @Scheduled(fixedDelay = 2000) public void doSomethingByFixedDelay() { System.out.println("do something"); } /** * 固定執(zhí)行觸發(fā)頻率 */ @Scheduled(fixedRate = 2000) public void doSomethingByFixedRate() { System.out.println("do something"); } }
Spring 定時(shí)任務(wù)原理
Cloud Native
運(yùn)行原理
Spring 定時(shí)任務(wù)核心邏輯主要在 spring-context 中的 scheduling 包中,其主要結(jié)構(gòu)包括:
定時(shí)任務(wù)解析:通過(guò) ScheduledTasksBeanDefinitionParser 對(duì) XML 定義任務(wù)配置解析;也可通過(guò) ScheduledAnnotationBeanPostProcessor對(duì)@Scheduled 注解進(jìn)行任務(wù)解析(常見模式)。
定時(shí)任務(wù)注冊(cè)登記:上述解析獲得的 Task 任務(wù)配置會(huì)被注冊(cè)登記至 ScheduledTaskRegistrar 中以備運(yùn)行使用。
任務(wù)定時(shí)運(yùn)行:完成所有任務(wù)注冊(cè)登記后,會(huì)通過(guò) TaskScheduler 正式地定時(shí)運(yùn)行相關(guān)任務(wù),底層通過(guò) JDK 的 ScheduledExecutorService 運(yùn)行任務(wù)。
業(yè)務(wù)邏輯會(huì)將被包裝在 ScheduledMethodRunnable 類中,其中包含了待執(zhí)行的目標(biāo)業(yè)務(wù)對(duì)象 Bean 和業(yè)務(wù)方法,該 Runnable 對(duì)象在運(yùn)行時(shí)會(huì)被提交至 ScheduledExecutorService 調(diào)度線程池完成任務(wù)的定時(shí)運(yùn)行。
從上圖可以看到真正要運(yùn)行的業(yè)務(wù)邏輯 ScheduledMethodRunnable 會(huì)被 ReschedulingRunnable、DelegatingErrorHandlingRunnable 做了代理擴(kuò)展,這兩層代理擴(kuò)展具有如下意義:
DelegatingErrorHandlingRunnable:為業(yè)務(wù)方法運(yùn)行異常進(jìn)行包裝處理,提供了自定義異常處理機(jī)制、解決 JDK 原生定時(shí)任務(wù)執(zhí)行異常后任務(wù)失效問(wèn)題。
ReschedulingRunnable:提供了擴(kuò)展的定時(shí)模式支持,可支持基于 Trigger 接口自定義實(shí)現(xiàn)獲取下次觸發(fā)時(shí)間定時(shí)調(diào)度,默認(rèn)提供的 Cron 定時(shí)通過(guò)此方式進(jìn)行擴(kuò)展實(shí)現(xiàn)。
定時(shí)模式
Spring 定時(shí)任務(wù) Task 類的模式主要可分為兩類:IntervalTask 和 TriggerTask。前者表示固定頻率間隔執(zhí)行,后者則采用 Trigger 觸發(fā)器模式實(shí)現(xiàn)定時(shí)調(diào)度,Cron 表達(dá)式配置為該模式實(shí)現(xiàn)。
FixedDelay:按固定延遲頻率執(zhí)行,任務(wù)下一次觸發(fā)時(shí)間=上一次執(zhí)行結(jié)束時(shí)間+Delay 延遲時(shí)間。
FixedRate:按固定頻率觸發(fā)執(zhí)行,任務(wù)下一次觸發(fā)時(shí)間=上一次觸發(fā)時(shí)間+Delay 延遲時(shí)間。如果上一次執(zhí)行方法不結(jié)束會(huì)阻塞下一次任務(wù)執(zhí)行。
Cron 表達(dá)式:按 Cron 表達(dá)式計(jì)算下一次觸發(fā)時(shí)間,任務(wù)下一次觸發(fā)時(shí)間=cron(上一次執(zhí)行結(jié)束時(shí)間)。
進(jìn)階擴(kuò)展
線程池運(yùn)行
默認(rèn)配置下底層運(yùn)行的線程池為單線程,單線程的運(yùn)行模型在任務(wù)量較多且觸發(fā)頻率較高的情況下,一旦某個(gè)任務(wù)發(fā)生阻塞會(huì)導(dǎo)致所有后續(xù)定時(shí)任務(wù)運(yùn)行阻斷,這對(duì)業(yè)務(wù)運(yùn)行帶來(lái)嚴(yán)重隱患。常見可采用如下方式:
配置定時(shí)執(zhí)行線程池:常見基于配置 Spring Boot 配置(spring.task.scheduling.pool.size=線程數(shù)),線程數(shù)大小取決于任務(wù)數(shù)及調(diào)度頻率合理配置。
配置異步任務(wù):在 spring context 中的 scheduling 模塊下提供了@EnableAsync 和@Async,可用于開啟任務(wù)異步執(zhí)行,實(shí)現(xiàn)定時(shí)調(diào)度線程池非阻塞運(yùn)行。該模式下存在一些不足之處:異常處理需要走異步調(diào)用的 AsyncUncaughtExceptionHandler 異常處理接口實(shí)現(xiàn),同步/異步定時(shí)任務(wù)異常處理機(jī)制不統(tǒng)一,另外異步模式增加了業(yè)務(wù)應(yīng)用的線程開銷。
@Scheduled(fixedDelay = 2000) @Async public void test() { System.out.println(DateUtil.now()+ " test."); }
異常統(tǒng)一處理
定時(shí)任務(wù)運(yùn)行可設(shè)置統(tǒng)一異常處理,基于 ErrorHandler 接口開發(fā)對(duì)應(yīng)異常處理實(shí)現(xiàn)類。對(duì)應(yīng)的異常實(shí)現(xiàn)處理類需要注入到核心的 ThreadPoolTaskScheduler 中,用戶可以通過(guò)自定義 TaskSchedulerCustomizer 方式來(lái)實(shí)現(xiàn) ErrorHandler 自定義異常處理 Bean 注入至 ThreadPoolTaskScheduler 中。
@Component public class DemoTaskSchedulerCustomizer implements TaskSchedulerCustomizer { @Override public void customize(ThreadPoolTaskScheduler taskScheduler) { taskScheduler.setErrorHandler(new DemoErrorHandler()); } private class DemoErrorHandler implements ErrorHandler { @Override public void handleError(Throwable throwable) { System.out.println("異常統(tǒng)一處理."); } } }
原生 Spring 定時(shí)任務(wù)在企業(yè)中遇到的問(wèn)題
Cloud Native
任務(wù)重復(fù)執(zhí)行
Spring 定時(shí)任務(wù),只要有注解就會(huì)執(zhí)行,在分布式場(chǎng)景下,所有機(jī)器代碼一致,會(huì)導(dǎo)致同一個(gè)任務(wù)在多臺(tái)機(jī)器上重復(fù)執(zhí)行。一般的解決方案是搶鎖觸發(fā),分布式鎖實(shí)現(xiàn)形式可采用 DB、ZK、Redis 等方式。
示例代碼如下:
@Component @EnableScheduling public class MyTask { /** * 每分鐘的第30秒跑一次 */ @Scheduled(cron = "30 * * * * ?") public void task1() throws Exception { String lockName = "task1"; if (tryLock(lockName)) { System.out.println("hello cron"); releaseLock(lockName); } else { return; } } private boolean tryLock(String lockName) { //TODO return true; } private void releaseLock(String lockName) { //TODO } }如上圖所示,當(dāng)任務(wù)觸發(fā)時(shí) 3 個(gè) server 會(huì)對(duì)任務(wù)搶鎖,僅獲得任務(wù)鎖的 server 才能執(zhí)行對(duì)應(yīng)任務(wù)業(yè)務(wù)邏輯。當(dāng)前的這個(gè)設(shè)計(jì),仔細(xì)一點(diǎn)的同學(xué)可以發(fā)現(xiàn),其實(shí)還是有可能導(dǎo)致任務(wù)重復(fù)執(zhí)行的。比如任務(wù)執(zhí)行的非???,A 這臺(tái)機(jī)器搶到鎖,執(zhí)行完任務(wù)后很快就釋放鎖了。B 這臺(tái)機(jī)器后搶鎖,還是會(huì)搶到鎖,再執(zhí)行一遍任務(wù)。
無(wú)管控?zé)o運(yùn)維
原生 Spring 定時(shí)任務(wù)沒有控制臺(tái),無(wú)法動(dòng)態(tài)的新增和修改定時(shí)任務(wù),如果要修改定時(shí)任務(wù)的配置(比如每分鐘跑一次改成每小時(shí)跑一次),必須修改代碼重新發(fā)布應(yīng)用。同時(shí)原生Spring定時(shí)任務(wù)也沒有運(yùn)維操作,不支持運(yùn)行一次任務(wù),任務(wù)失敗了也不支持重跑任務(wù)。
如果要自研的可視化控制臺(tái)來(lái)實(shí)現(xiàn)整套任務(wù)可視化管控體系,需要一定的前后端研發(fā)成本和服務(wù)部署成本投入。對(duì)于需要自建的用戶而言,可參考以下需求功能進(jìn)行自有平臺(tái)建設(shè):
任務(wù)的可視化動(dòng)態(tài)配置
任務(wù)執(zhí)行運(yùn)行詳細(xì)信息的可視化查看
任務(wù)執(zhí)行日志、執(zhí)行調(diào)用鏈、調(diào)度觸發(fā)的可視化查詢分析
業(yè)務(wù)應(yīng)用間任務(wù)信息配置權(quán)限隔離
無(wú)業(yè)務(wù)失敗通知能力
對(duì)于完整企業(yè)級(jí)定時(shí)任務(wù)運(yùn)用方案中,報(bào)警通知能力必不可少,任務(wù)跑失敗了需要及時(shí)通知到用戶,否則可能產(chǎn)生故障。
原生 Spring 定時(shí)任務(wù)不支持報(bào)警通知能力,如果要自研,可以參考上一章節(jié)中《異常統(tǒng)一處理》對(duì)任務(wù)失敗的信息進(jìn)行收集,構(gòu)建相應(yīng)的異常處理機(jī)制(包括對(duì)接各類報(bào)警平臺(tái)進(jìn)行異常消息通知處理,定義異常等級(jí)和類別進(jìn)行不同的通知策略),然后進(jìn)行定時(shí)任務(wù)報(bào)警通知。
無(wú)在線排查分析能力
定時(shí)任務(wù)在運(yùn)行過(guò)程中會(huì)存在各種各樣的問(wèn)題,比如:執(zhí)行失敗、執(zhí)行耗時(shí)、執(zhí)行卡住等,這些都需要在后期實(shí)際運(yùn)維去定位快速分析。在對(duì)應(yīng)分析過(guò)程中沒有高效在線排查能力的話將遇到很多棘手的問(wèn)題:
集群中任務(wù)對(duì)應(yīng)時(shí)間點(diǎn)是跑在哪個(gè)機(jī)器上無(wú)從可知
需要在大量的業(yè)務(wù)應(yīng)用日志中去檢索對(duì)應(yīng)時(shí)點(diǎn)的定時(shí)任務(wù)執(zhí)行日志,需要自行對(duì)接日志服務(wù)改善
如果任務(wù)涉及多個(gè)跨服務(wù)調(diào)用,無(wú)法定位執(zhí)行異常點(diǎn)或執(zhí)行耗時(shí)點(diǎn),需要自建全鏈路追蹤來(lái)支持
阿里云 Spring 定時(shí)任務(wù)企業(yè)級(jí)解決方案
Cloud Native
接下來(lái)主要講下如何利用公有云上任務(wù)調(diào)度 SchedulerX 輕松接入基于 Spring 開發(fā)的定時(shí)任務(wù)。前面聊了基于 Spring 原生功能在使用過(guò)程中面臨的問(wèn)題及需要自行處理解決的相關(guān)方案,可以看到僅針對(duì)企業(yè)級(jí)最基礎(chǔ)的運(yùn)用場(chǎng)景下就需要花費(fèi)較多的改造投入及相關(guān)服務(wù)后續(xù)運(yùn)維投入。通過(guò)接入 SchedulerX 任務(wù)調(diào)度平臺(tái),原本 Spring 定時(shí)任務(wù)使用者可無(wú)縫且 0 改造獲得企業(yè)級(jí)運(yùn)用所需能力,同時(shí)降低了自研部署運(yùn)維定時(shí)服務(wù)相關(guān)組件的技術(shù)成本。
如何接入
對(duì)于 SchedulerX 新用戶而言接入僅需三步(參考附件接入手冊(cè)):
依賴 SchedulerX 的 Spring Boot 版 SDK 完成調(diào)度平臺(tái)接入(版本>=1.7.2,老用戶僅升級(jí) SDK 版本即可)
配置文件添加配置項(xiàng),配置開啟后 Spring 定時(shí)調(diào)度器將不運(yùn)行相關(guān)任務(wù)(未配置情況下,不會(huì)主動(dòng)接管原 Spring 定時(shí)任務(wù)運(yùn)行,在配置開啟前不會(huì)影響原本定時(shí)任務(wù)業(yè)務(wù)運(yùn)行)
# 配置表示由SchedulerX接管Spring定時(shí)任務(wù)運(yùn)行 spring.schedulerx2.task.scheduling.scheduler=schedulerx
控制臺(tái)上在對(duì)應(yīng)應(yīng)用分組下創(chuàng)建任務(wù)配置定時(shí)觸發(fā)。也可以選擇開啟自動(dòng)同步任務(wù)配置方式(可選)
# 自動(dòng)同步Spring定時(shí)任務(wù)至調(diào)度平臺(tái),無(wú)需單獨(dú)手動(dòng)創(chuàng)建(默認(rèn)不開啟) spring.schedulerx2.task.scheduling.sync=true
接入優(yōu)勢(shì)
白屏管控和運(yùn)維
提供白屏控制臺(tái)可以動(dòng)態(tài)新增、修改、啟用、禁用任務(wù),支持運(yùn)行一次、原地重跑、重刷數(shù)據(jù)、停止任務(wù)、標(biāo)記成功等運(yùn)維操作。
可視化在線排查問(wèn)題
支持執(zhí)行記錄查看、執(zhí)行業(yè)務(wù)日志查詢、執(zhí)行全鏈路追蹤。
豐富的報(bào)警通知
SchedulerX 提供豐富的報(bào)警通知能力,支持短信、電話、郵件、webhook 報(bào)警,支持報(bào)警聯(lián)系人組和報(bào)警歷史,可白屏動(dòng)態(tài)配置。
其他優(yōu)勢(shì)
無(wú)改造成本的平臺(tái)接入方案。
無(wú)需額外獨(dú)立運(yùn)維調(diào)度服務(wù)平臺(tái)或其他第三方組件服務(wù)。
任務(wù)運(yùn)行在集群環(huán)境中具備穩(wěn)定高可靠支持,規(guī)避了原生框架存在的重復(fù)執(zhí)行問(wèn)題,具備故障自動(dòng)轉(zhuǎn)移能力。
在企業(yè)內(nèi)多個(gè)團(tuán)隊(duì)可共享一套平臺(tái)使用,通過(guò)命名空間和應(yīng)用分組實(shí)現(xiàn)各團(tuán)隊(duì)任務(wù)配置數(shù)據(jù)隔離及環(huán)境隔離。
總結(jié)
Cloud Native
本文主要從 Spring 定時(shí)任務(wù)的運(yùn)行機(jī)制進(jìn)行剖析闡述,并對(duì)如何擴(kuò)展框架原生能力以滿足企業(yè)級(jí)生產(chǎn)環(huán)境運(yùn)行定時(shí)任務(wù)所需各種場(chǎng)景提出了相應(yīng)的建議,用戶可作參考構(gòu)建自己內(nèi)部定時(shí)任務(wù)方案。同時(shí)就阿里云上提供的任務(wù)調(diào)度服務(wù)如何接入 Spring 定時(shí)任務(wù)的運(yùn)行進(jìn)行講解,并簡(jiǎn)單展示了接入后所帶來(lái)的企業(yè)級(jí)能力。最后歡迎有定時(shí)任務(wù)業(yè)務(wù)需求用戶可先通過(guò)基礎(chǔ)免費(fèi)額度體驗(yàn)感受云上服務(wù)帶來(lái)便捷。
審核編輯:湯梓紅
-
spring
+關(guān)注
關(guān)注
0文章
340瀏覽量
14343
原文標(biāo)題:說(shuō)說(shuō)Spring定時(shí)任務(wù)如何大規(guī)模企業(yè)級(jí)運(yùn)用
文章出處:【微信號(hào):OSC開源社區(qū),微信公眾號(hào):OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論