做爲後端開發人員,咱們總會遇到這樣的業務場景:每週同步一批數據;每半個小時檢查一遍服務器運行情況;天天早上八點給用戶發送一份包含今日待辦事項的郵件,等等。java
這些場景中都離不開「定時器」,就像一個定好時間規則的鬧鐘,它會在指定時間觸發,執行咱們想要定義的調度任務。那麼咱們今天就來數一下,那些年咱們用過的「定時調度」。linux
從剛工做就一直使用oracle數據庫,最先接觸的定時任務就是oracle數據庫的job。job有定時執行的功能,能夠在指定的時間點或天天的某個時間點自行執行任務。 並且oracle從新啓動後,job會繼續運行,不用從新啓動。spring
並且job的機制很是完備,能夠查詢相關的表或視圖,查詢job的定時規則和執行狀況。缺點是做爲oracle數據庫層面的工具,自定義功能擴展,二次開發的難度比較大。shell
1.1 建立job數據庫
DECLARE job NUMBER; BEGIN sys.dbms_job.submit(job => job, what => 'prc_name;', --執行的存儲過程的名字 next_date => to_date('22-11-2013 09:09:41', 'dd-mm-yyyy hh24:mi:ss'), --下一次執行時間 interval =>'sysdate+1/24'); --天天24小時,即每小時運行prc_name過程一次 END; -- job參數是輸出參數,由submit()過程返回的binary_ineger,這個值用來惟一標識一個工做。通常定義一個變量接收,能夠去user_jobs視圖查詢job值。 -- what參數是將被執行的PL/SQL代碼塊,存儲過程名稱等。 -- next_date參數指識什麼時候將運行這個工做。 -- interval參數什麼時候這個工做將被重執行
1.2 刪除jobsegmentfault
DECLARE BEGIN dbms_job.remove(1093); -- 1093爲當前須要刪除的 job 值 COMMIT; END;
1.3 查詢jobwindows
-- 查詢當前用戶的job select * from user_jobs; -- 查詢全部job select * from dba_jobs; -- 查詢全部運行中的job select * from dba_jobs_running;
crond 是linux下用來週期性的執行某種任務或等待處理某些事件的一個守護進程,與windows下的計劃任務相似,當安裝完成操做系統後,默認會安裝此服務 工具,而且會自動啓動crond進程,crond進程每分鐘會按期檢查是否有要執行的任務,若是有要執行的任務,則自動執行該任務。後端
cron是服務名稱,crond是後臺進程,crontab則是定製好的計劃任務表。大部分linux系統默認都安裝了cron,能夠檢查一下。centos
-- 檢查Crontab工具是否安裝 crontab -l -- 檢查crond服務是否啓動 service crond status -- centos安裝 yum install vixie-cron yum install crontabs
crontab基本操做命令springboot
-- 列出某個用戶cron服務的詳細內容 crontab -l -- 編輯某個用戶的cron服務 crontab -e
crontab表達式格式
{minute} {hour} {day-of-month} {month} {day-of-week} {full-path-to-shell-script} minute: 區間爲 0 – 59 hour: 區間爲0 – 23 day-of-month: 區間爲0 – 31 month: 區間爲1 – 12. 1 是1月. 12是12月 Day-of-week: 區間爲0 – 7. 週日能夠是0或7. 在以上各個字段中,還可使用如下特殊字符: 星號(*):表明全部可能的值,例如month字段若是是星號,則表示在知足其它字段的制約條件後每個月都執行該命令操做。 逗號(,):能夠用逗號隔開的值指定一個列表範圍,例如,「1,2,5,7,8,9」 中槓(-):能夠用整數之間的中槓表示一個整數範圍,例如「2-6」表示「2,3,4,5,6」 正斜線(/):能夠用正斜線指定時間的間隔頻率,例如「0-23/2」表示每兩小時執行一次。同時正斜線能夠和星號一塊兒使用,例如*/10,若是用在minute字段,表示每十分鐘執行一次。
推薦一個crontab表達式的校驗網站(https://tool.lu/crontab/)
Timer是jdk中提供的一個定時器工具,使用的時候會在主線程以外起一個單獨的線程執行指定的計劃任務,能夠指定執行一次或者反覆執行屢次。
//只執行一次 public void schedule(TimerTask task, long delay); public void schedule(TimerTask task, Date time); //循環執行 // 在循環執行類別中根據循環時間間隔又能夠分爲兩類 public void schedule(TimerTask task, long delay, long period) ; public void schedule(TimerTask task, Date firstTime, long period) ; public void scheduleAtFixedRate(TimerTask task, long delay, long period) public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
TimerTask是一個實現了Runnable接口的抽象類,表明一個能夠被Timer執行的任務。TimerTask類是一個抽象類,由Timer 安排爲一次執行或重複執行的任務。它有一個抽象方法run()方法,該方法用於執行相應計時器任務要執行的操做。所以每個具體的任務類都必須繼承TimerTask,而後重寫run()方法。
另外它還有兩個非抽象的方法
-- 取消此計時器任務 boolean cancel() -- 返回此任務最近實際執行的安排執行時間 long scheduledExecutionTime()
固然,通常使用Timer的比較少,由於它的缺點比較明顯:
因此通常使用ScheduledExecutorService替代Timer。
ScheduledExecutorService:也是jdk自帶的一個基於線程池設計的定時任務類。其每一個調度任務都會分配到線程池中的一個線程執行,因此其任務是併發執行的,互不影響。
Timer和ScheduledExecutorService都是屬於jdk層面上實現定時調度的類,功能還不足以讓咱們滿意,那麼如今介紹一個比較完善的定時調度工具 - SpringTask,是Spring提供的,支持註解和配置文件形式,支持crontab表達式,使用簡單但功能強大。我我的很是喜歡SpringTask,僅僅是由於支持crontab表達式。
在springboot裏面使用方式很是簡單:
默認的簡單的使用步驟只有以上兩步,可是SpringTask的默認使用方式也有一些不足:
問題1的解決方式,能夠經過自定義 TaskExecutor來修改當前的線程池。問題2,則能夠直接使用 threadPoolTaskScheduler類實現自定義的定時調度規則。
附解決兩個問題的源碼 TaskTimer.class
@Component public class TaskTimer { @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; @Autowired private TaskRepo taskRepo; /** * ***定時引擎*** * * 實例化一個線程池任務調度類 * 默認 ThreadPoolTaskScheduler 的 poolSize 爲1,相似於newSingleThreadExecutor 單線程模式,只能執行完一個調度,再執行其餘調度 * 須要自定義擴展poolSize,容許必定程度的 多線程並行場景 * * @return */ @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(100); threadPoolTaskScheduler.setThreadNamePrefix("Thread - "); threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true); threadPoolTaskScheduler.setAwaitTerminationSeconds(60); return threadPoolTaskScheduler; } /** * 啓動定時調度 * @param taskCode * @param cron * @param runnable * @return */ public boolean start(String taskCode, String cron,Runnable runnable){ ScheduledFuture<?> currentFuture=taskRepo.findTask(taskCode); //已存在的調度,沒法再建立 if(currentFuture!=null){ throw new RuntimeException("調度\""+taskCode+"\"已存在,沒法再建立"); } //建立新的調度,並加入 taskMap currentFuture = threadPoolTaskScheduler.schedule(runnable,new CronTrigger(cron)); if (currentFuture!=null){ this.taskRepo.addTask(taskCode,currentFuture); return true; } throw new RuntimeException("任務啓動失敗!!!"); } /** * 暫停定時調度 * @param taskCode * @return */ public boolean stop(String taskCode) { //taskId 不存在的,沒法中止,只能修改 ScheduledFuture<?> currentFuture=this.taskRepo.findTask(taskCode); if(currentFuture!=null){ return currentFuture.cancel(true); } return true; } /** * 刪除定時調度 * @param taskCode * @return */ public boolean remove(String taskCode){ ScheduledFuture<?> currentFuture=this.taskRepo.findTask(taskCode); if(currentFuture!=null){ currentFuture.cancel(true); taskRepo.removeTask(taskCode); return true; } return false; } /** * 修改定時調度 * @param taskCode * @param cron * @param runnable * @return */ public boolean update(String taskCode,String cron,Runnable runnable){ ScheduledFuture<?> currentFuture=this.taskRepo.findTask(taskCode); //已存在的定時調度,先中止,再新增,並更新 新的ScheduledFuture if(currentFuture!=null) { currentFuture.cancel(true); } currentFuture= threadPoolTaskScheduler.schedule(runnable,new CronTrigger(cron)); if(currentFuture!=null){ this.taskRepo.addTask(taskCode,currentFuture); return true; } return false; } }
Quartz是一個徹底由 Java 編寫的開源做業調度框架,爲在 Java 應用程序中進行做業調度提供了簡單卻強大的機制。它是一個功能強大、十分紅熟的重量級產品,還支持負載均衡,實現分佈式調度。
不過,對於Quartz的安裝你要多花點功夫了,從數據庫要建哪些表,到應用程序該如何部署。對於這樣一個龐大的產品,本篇文章就不附上它的使用說明書了。