Spring -- 定時任務調度的發展
- 2020 年 2 月 10 日
- 筆記
Java領域的調度最早一般認為是Timer,接著由Quratz創造調度器(Scheduler)、任務(Job)和觸發器(Trigger)三個核心概念後開始發展,接著在JDK1.5時ScheduledThreadPoolExecutor出現,逐漸成為主流的單機定時調度方式,Spring的定時任務底層適配了Quratz以及ScheduledThreadPoolExecutor,提供更加方便的使用形式,並沒有提供新的調度器實現,再接著發展則是抽離出來任務觸發部分,獨立集群部署,以應對數以萬計的定時任務,即以Elastic-job,xxl-job等為代表的分散式調度平台。本文主要描述單機調度與分散式調度的常見實現原理,由於平台眾多,如有錯誤還請指出。
Timer調度
Timer雖然古老,但是調度該有的東西基本都有,因此仍然值得一說。其使用如下所示,TimerTask為Job,提交一個job,即提交到執行隊列(最小堆)中一個任務,Timer本身是Scheduler,也是Trigger,其內部持有一個執行緒,該執行緒會循環掃描任務優先順序隊列,發現可執行任務則執行,未發現或任務未到時間則等待下去。
清單1: Timer調度示例
// 相當於創建一個調度器 Timer timer = new Timer(); // 提交一個job任務 timer.schedule(new TimerTask() { @Override public void run() { System.out.println(111); } }, 2000L);至於內部優先順序隊列實現,以及任務循環實現與ScheduledThreadPoolExecutor類似,因此在下面ScheduledThreadPoolExecutor中詳細說明。
ScheduledThreadPoolExecutor調度
Timer的缺陷是單執行緒執行,一個任務阻塞就會導致後續任務延遲,ScheduledThreadPoolExecutor簡單來說為多執行緒版的Timer,除此之外還提供了任務隔離,異常處理等功能。 ScheduledThreadPoolExecutor為ThreadPoolExecutor的子類,本質上執行流程仍是執行緒池的那一套邏輯,核心在於BlockingQueue的實現類DelayedWorkQueue。DelayedWorkQueue在數據結構上和Timer保持一致,為小根堆,根節點為當前要執行的任務,每次執行緒去獲取根節點即可拿到最近需要執行的任務。
清單2:小根堆示意
針對固定頻率任務比如每5分鐘一次,如何實現循環呢?答案是當一個任務執行結束前,計算出下次執行時間,然後重新添加到隊列,隊列則自動根據優先順序調整順序。多執行緒是針對多任務並發執行,針對單個任務仍然可以看作是單執行緒執行,同樣如果任務執行時間超過執行周期,那麼也會導致任務延遲。
ps:這裡ScheduledThreadPoolExecutor針對多執行緒下任務搶佔執行緒切換消耗做了leader/follow模式優化,有興趣可以看下源碼。
Spring調度
在Spring中可以很容易用@Scheduled註解開啟一個定時任務,其內部適配了Quratz以及ScheduledThreadPoolExecutor兩種實現,默認為ScheduledThreadPoolExecutor,其中針對ScheduledThreadPoolExecutor額外提供了cron定時形式,實現原理與固定類似,如下圖所示,在ReschedulingRunnable類中執行完畢後,會再次開啟下一個定時任務。
PS:關於Spring如何切入到Bean註冊流程,掃描出對應@Scheduled註解對應的方法,註冊對應定時器,這一流程也值得學習,有興趣的同學可以從ScheduledAnnotationBeanPostProcessor類開始看。
時間輪調度
上述的調度實際上都是依賴於優先順序隊列,除此之外還有time wheel演算法,如下圖所示,原理是把時間按照bucket進行分配,比如下面一輪8個格子,假設一個格子1秒,指針從0開始走,當走到1時,發現其對應兩個任務需要分別走2輪和4輪後才能執行,因此繼續下去,當再次走到0時,對應所有任務輪數減1,因此針對該bucket上的任務需要 round() * 8 + N 秒才能被執行。
單時間輪會因為調度周期導致任務的round()變得很大,需要走很多輪才能被執行,同時一個bucket可能掛在很多個任務,導致效率降低,因此出現了變種演算法層級時間輪,即多個時間輪嵌套,上級時間輪對應的round()變為0後,則下方到下級,直到最底層時間輪才被執行。
分散式調度
在分散式環境下,單機調度有著複雜的問題,比如多台機器一起觸發,需要業務上做冪等,或者使用分散式鎖+重試策略,以防萬一還需要提供手動觸發能力。對於量比較大的計算,無法充分發揮集群優勢,只能落到單台機器執行等等問題。 無論單機還是分散式,調度框架模型始終調度器(Scheduler)、任務(Job)和觸發器(Trigger)三大組件,而分散式的策略是把觸發器(Trigger)集群化,任務觸發消息通過其他服務轉交給對應業務系統,業務系統承擔調度器(Scheduler)、任務(Job),執行完畢後回饋給觸發器。
xxl-job架構圖
參考
- 版權聲明: 感謝您的閱讀,本文由屈定's Blog版權所有。如若轉載,請註明出處。
- 文章標題: Spring -- 定時任務調度的發展
- 文章鏈接: https://mrdear.cn/2019/09/22/framework/spring/Spring--schedule/