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

SpringBoot如何實現(xiàn)動態(tài)增刪啟停定時任務

Python定時任務的實現(xiàn)方式
解析Golang定時任務庫gron設計和原理
求一種SpringBoot定時任務動態(tài)管理通用解決方案
Spring Boot中整合兩種定時任務的方法

評論