參考:http://www.importnew.com/29864.html
上兩章節,我們簡單的講解了關于異步調用和異步請求相關知識點。這一章節,我們來講講開發過程也是經常會碰見的定時任務。比如每天定時清理無效資料、定時發送短信、定時發送郵件、支付系統中的定時對賬等等,往往都會定義一些定時器,進行此業務的開發。是以,本章節介紹下在 SpringBoot
中定時任務如何使用及一點分布式定時服務的思考總結。
一點知識
在
JAVA
開發領域,目前可以通過以下幾種方式進行定時任務:
- Timer:jdk中自帶的一個定時排程類,可以簡單的實作按某一頻度進行任務執行。提供的功能比較單一,無法實作複雜的排程任務。
- ScheduledExecutorService:也是jdk自帶的一個基于線程池設計的定時任務類。其每個排程任務都會配置設定到線程池中的一個線程執行,是以其任務是并發執行的,互不影響。
- Spring Task:
提供的一個任務排程工具,支援注解和配置檔案形式,支援Spring
表達式,使用簡單但功能強大。Cron
- Quartz:一款功能強大的任務排程器,可以實作較為複雜的排程功能,如每月一号執行、每天淩晨執行、每周五執行等等,還支援分布式排程,就是配置稍顯複雜。
Quartz
,早前用過1.6版本的,更新到2.x及以上版本後基本沒怎麼接觸了,原來還有倒騰過結合 Kettle
做了一些動态的定時抽取資料啥的還編寫過一個 Cron
表達式編輯器,現在基本忘記了。。等有機會,再次深入學習後再來單獨分享一些關于的 Quartz
心得吧。 基于JDK方式實作簡單定時
剛剛有介紹過,基于
JDK
方式一共有兩種:
Timer
和
ScheduledExecutorService
。接下來,就簡單講解下這兩種方式。
Timer
Timer
是jdk提供的
java.util.Timer
類。
簡單示例:1 2 3 4 5 6 7 8 9 10 11 12 | |
啟動後,通路即可看見控制台周期性輸出資訊了:
1 2 3 4 5 6 | |
1、在特定時間執行任務,隻執行一次
1 | |
2、在特定時間之後執行任務,隻執行一次
1 | |
3、指定第一次執行的時間,然後按照間隔時間,重複執行
1 | |
4、在特定延遲之後第一次執行,然後按照間隔時間,重複執行
1 | |
5、第一次執行之後,特定頻率執行,與3同
1 | |
6、在delay毫秒之後第一次執行,後按照特定頻率執行
1 | |
參數:
- delay: 延遲執行的毫秒數,即在delay毫秒之後第一次執行
- period:重複執行的時間間隔
取消任務使用:
timer.cancel()
方法即可登出任務。
此類相對用的較少了,簡單了解下。
ScheduledExecutorService
簡單示例:可以說是
ScheduledExecutorService
的替代類,因為
Timer
不支援多線程,任務是串行的,而且也不捕獲異常,假設某個任務異常了,整個
Timer
就無法運作了。
Timer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
啟動後,可看見控制台按設定的頻率輸出:
1 2 3 4 5 6 | |
可同時設定多個任務,隻需再次設定
scheduleAtFixedRate
即可。
常用方法說明:- ScheduleAtFixedRate:
1 | |
參數說明:
- command:執行線程
- initialDelay:初始化延時
- period:兩次開始執行最小間隔時間
- unit:計時機關
- ScheduleWithFixedDelay:
1 | |
參數說明:
- command:執行線程
- initialDelay:初始化延時
- delay:前一次執行結束到下一次執行開始的間隔時間(間隔執行延遲時間)
- unit:計時機關
基于SpingTask實作定時任務
使用
SpringTask
在
SpringBoot
是很簡單的,使用
@Scheduled
注解即可輕松搞定。
0.啟動類,加入
@EnableScheduling
讓注解
@Scheduled
生效。
1 2 3 4 5 6 7 8 9 10 | |
1.編寫一個排程類,系統啟動後自動掃描,自動執行。
1 2 3 4 5 6 7 8 9 10 11 12 | |
2.啟動後,控制台可就看見每5秒一次輸出了:
1 2 3 4 5 6 | |
使用都是簡單的,現在我們來看看注解
@Scheduled
的參數意思:
- fixedRate:定義一個按一定頻率執行的定時任務
- fixedDelay:定義一個按一定頻率執行的定時任務,與上面不同的是,改屬性可以配合
, 定義該任務延遲執行時間。initialDelay
- cron:通過表達式來配置任務執行時間
Cron表達式詳解
一個 cron
表達式有至少6個(也可能7個)有空格分隔的時間元素。
依次順序如下表所示:
字段 | 允許值 | 允許的特殊字元 |
---|---|---|
秒 | 0~59 | , – * / |
分 | 0~59 | , – * / |
小時 | 0~23 | , – * / |
日期 | 1-31 | , – * ? / L W C |
月份 | 1~12或者JAN~DEC | , – * / |
星期 | 1~7或者SUN~SAT | , – * ? / L C # |
年(可選) | 留白,1970~2099 | , – * / |
- 0/1 * * * * ?:每秒執行一次
- 0 0 2 1 * ? : 表示在每月的1日的淩晨2點調整任務
- 0 0 10,14,16 ? :每天上午10點,下午2點,4點
- 0 0 12 * * ? : 每天中午12點觸發
- 0 15 10 ? * MON-FRI : 周一至周五的上午10:15觸發
更多表達式,可通路:cron.qqe2.com/ 進行線上表達式編寫。簡單明了。
自定義線程池
從控制台輸出可以看見,多任務使用的是同一個線程。可結合上章節的異步調用來實作不同任務使用不同的線程進行任務執行。
0.編寫配置類,同時啟用
@Async
注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
1.排程類上加入
@Async
。
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
再次啟動程式,可看見控制台輸出,任務已經是不同線程下執行了:
1 2 3 4 5 | |
動态添加定時任務
使用注解的方式,無法實作動态的修改或者添加新的定時任務的,這個使用就需要使用程式設計的方式進行任務的更新操作了。可直接使用或者
ThreadPoolTaskScheduler
接口進行自定義定時任務建立。
SchedulingConfigurer
ThreadPoolTaskScheduler
是
ThreadPoolTaskScheduler
的核心實作類,該類提供了大量的重載方法進行任務排程。這裡簡單示例下,具體的大家自行搜尋下,用的少不太了解呀。
SpringTask
0.建立一個
ThreadPoolTaskScheduler
類。
1 2 3 4 5 6 7 8 9 10 11 12 | |
1.編寫一個控制類,動态設定定時任務:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
2.啟動後,通路接口,即可看見控制台每3秒輸出一次:
1 2 3 4 | |
SchedulingConfigurer
此類十個接口,直接實作其 configurerTasks
方法即可。
0.編寫配置類:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
1.啟動後,控制台也可以看見每3秒輸出一次:
1 2 3 | |
基于Quartz實作定時排程
由于本章節是基于
SpringBoot 1.x
版本的,是以沒有基于
Quartz
的
starter
配置,這裡直接引入了
Quartz
相關依賴包來內建。
題外話:原本使用
SpringMvc
時,一般上都是通過
xml
檔案,配置其
org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
類進行具體執行任務的配置,指定執行的對象和方法。然後通過設定
CronTriggerFactoryBean
或者
SimpleTriggerFactoryBean
設定定時器,最後通過
org.springframework.scheduling.quartz.SchedulerFactoryBean
加入排程的
trigger
。是以,我們就使用
javaConfig
方式進行簡單內建下。
0.加入pom依賴
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
1.編寫配置類。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | |
2.啟動後,可以看見控制台以每3秒執行一次輸出:
1 2 3 | |
Quartz
的詳細用法,再次不表了。好久沒有使用過了。有機會再來詳細闡述吧。 分布式排程服務淺談
在單機模式下,定時任務是沒什麼問題的。但當我們部署了多台服務,同時又每台服務又有定時任務時,若不進行合理的控制在同一時間,隻有一個定時任務啟動執行,這時,定時執行的結果就可能存在混亂和錯誤了。
這裡簡單的說說相關的解決方案吧,一家之言,希望大家能提出自己的見解,共同進步!
- 剝離所有定時任務到一個工程:此方案是最簡單的,在定時任務相對較小,并發任務不多時,可以使用此方案。簡單也容易維護。當定時任務牽扯的業務越來越多,越來越雜時,維護量就成本增加了,工程會越來越臃腫,此方案就不實用了。
- 利用
叢集方案:本身Quartz
是支援通過資料庫實作叢集的,以下是其叢集架構圖:Quartz
叢集架構圖
其實作原理也相對簡單:通過資料庫實作任務的持久化,儲存定時任務的相關配置資訊,以保證下次系統啟動時,定時任務能自動啟動。同時,通過資料庫
行鎖(for update)
機制,控制一個任務隻能被一個執行個體運作,隻有擷取鎖的執行個體才能運作任務,其他的隻能等待,直到鎖被釋放。這種方式有些弊端,就是依賴了資料庫,同時也需要保證各伺服器之間的時間需要同步,不然也是會混亂的。
現在
Quartz
也有基于
Redis
的叢集方案,有興趣的可以搜尋下。
- 分布式鎖:可通過使用
或者Redis
實作一個分布式鎖的機制,使得隻有擷取到鎖的執行個體方能運作定時任務,避免任務重複執行。可檢視下開源的基于ZooKeeper
實作的分布式鎖項目:Redis
。github位址:github.com/redisson/re…有興趣的同學可以了解下。redisson
- 統一排程中心:
可建構一個
純粹
的定時服務,隻有
定時器
相關配置,比如
定時時間
,
定時排程的api接口
或者
http
服務,甚至是統一注冊中心下的服務類,如dubbo服務等。而具體的任務執行操作都在各自業務方系統中,排程中心
隻負責接口的調用
,具體實作還是在業務方。這種方案相對來說比較通用,實作起來也簡單。就是需要業務方進行約定程式設計,或者對外提供一個api接口。
當然,為了實作定時任務的自動發現和注冊功能,還是需要規範一套規則來實作自動注冊功能。簡單來說,以
Dubbo
服務為例,可以定義一個
定時任務接口類
,排程中心隻需要擷取所有實作此接口的服務,同時通過服務的相關配置(排程時間、失敗政策等)進行相關定時操作。或者編寫一個服務注冊與發現的用戶端,通過
Spring
擷取到實作此接口的所有實作類,上送到排程中心。
而且,統一排程中心,還可以對所有的定時任務的排程情況進行有效監控,日志記錄等,也可以約定接口,讓定時任務回傳定時結果,做到全局把控的目的。
轉載于:https://juejin.im/post/5c94b4a7e51d4536e85c39ab