在
JAVA
開發領域,目前可以通過以下幾種方式進行定時任務:
- Timer:jdk中自帶的一個定時調度類,可以簡單的實現按某一頻度進行任務執行。提供的功能比較單一,無法實現復雜的調度任務。
- ScheduledExecutorService:也是jdk自帶的一個基於線程池設計的定時任務類。其每個調度任務都會分配到線程池中的一個線程執行,所以其任務是並發執行的,互不影響。
- Spring Task:
Spring
提供的一個任務調度工具,支持注解和配置文件形式,支持Cron
表達式,使用簡單但功能強大。ThreadPoolTaskScheduler
- Quartz:一款功能強大的任務調度器,可以實現較為復雜的調度功能,如每月一號執行、每天凌晨執行、每周五執行等等,還支持分布式調度,就是配置稍顯復雜。
在單機模式下,定時任務是沒什么問題的。但當我們部署了多台服務,同時又每台服務又有定時任務時,若不進行合理的控制在同一時間,只有一個定時任務啟動執行,這時,定時執行的結果就可能存在混亂和錯誤了。
這里簡單的說說相關的解決方案吧,一家之言,希望大家能提出自己的見解,共同進步!
- 剝離所有定時任務到一個工程:此方案是最簡單的,在定時任務相對較小,並發任務不多時,可以使用此方案。簡單也容易維護。當定時任務牽扯的業務越來越多,越來越雜時,維護量就成本增加了,工程會越來越臃腫,此方案就不實用了。
- 利用
Quartz
集群方案:本身Quartz
是支持通過數據庫實現集群的,以下是其集群架構圖:
其實現原理也相對簡單:通過數據庫實現任務的持久化,保存定時任務的相關配置信息,以保證下次系統啟動時,定時任務能自動啟動。同時,通過數據庫行鎖(for update)
機制,控制一個任務只能被一個實例運行,只有獲取鎖的實例才能運行任務,其他的只能等待,直到鎖被釋放。這種方式有些弊端,就是依賴了數據庫,同時也需要保證各服務器之間的時間需要同步,不然也是會混亂的。
現在Quartz
也有基於Redis
的集群方案,有興趣的可以搜索下。
- 分布式鎖:可通過使用
Redis
或者ZooKeeper
實現一個分布式鎖的機制,使得只有獲取到鎖的實例方能運行定時任務,避免任務重復執行。可查看下開源的基於Redis
實現的分布式鎖項目:redisson
。github地址:https://github.com/redisson/redisson有興趣的同學可以了解下。 - 統一調度中心:
可構建一個純粹
的定時服務,只有定時器
相關配置,比如定時時間
,定時調度的api接口
或者http
服務,甚至是統一注冊中心下的服務類,如dubbo服務等。而具體的任務執行操作都在各自業務方系統中,調度中心只負責接口的調用
,具體實現還是在業務方。這種方案相對來說比較通用,實現起來也簡單。就是需要業務方進行約定編程,或者對外提供一個api接口。
當然,為了實現定時任務的自動發現和注冊功能,還是需要規范一套規則來實現自動注冊功能。簡單來說,以Dubbo
服務為例,可以定義一個定時任務接口類
,調度中心只需要獲取所有實現此接口的服務,同時通過服務的相關配置(調度時間、失敗策略等)進行相關定時操作。或者編寫一個服務注冊與發現的客戶端,通過Spring
獲取到實現此接口的所有實現類,上送到調度中心。
而且,統一調度中心,還可以對所有的定時任務的調度情況進行有效監控,日志記錄等,也可以約定接口,讓定時任務回傳定時結果,做到全局把控的目的。
以上就是對分布式調度的一點理解,有錯誤的地方還望指正,有更好的方案也希望能分享下。
參考資料
- https://www.cnblogs.com/yank/p/3955322.html
- https://blog.csdn.net/tsyj810883979/article/details/8481621
- https://www.cnblogs.com/javahr/p/8318728.html
- http://www.quartz-scheduler.org/documentation/quartz-2.1.x/quick-start.html
- https://spring.io/guides/gs/scheduling-tasks/
總結
本章節主要是講解了通過不同的方式實現定時任務。對於定時任務而言,本身是門大學問,一倆篇文章是講不完的。像
SpringTask
和Quartz
都是很強大的調度器,兩者很相似,像如何實現任務的動態修改調度周期,動態停止相關任務,調度任務的監控,這些本文章都沒有涉及。還希望有相關需求的同學自行搜索相關資料了。