摘要:也是自帶的一個基于線程池設(shè)計的定時任務(wù)類。問題的解決方式,可以通過自定義來修改當(dāng)前的線程池。問題,則可以直接使用類實(shí)現(xiàn)自定義的定時調(diào)度規(guī)則。
定時調(diào)度
作為后端開發(fā)人員,我們總會遇到這樣的業(yè)務(wù)場景:每周同步一批數(shù)據(jù);每半個小時檢查一遍服務(wù)器運(yùn)行狀況;每天早上八點(diǎn)給用戶發(fā)送一份包含今日待辦事項的郵件,等等。
這些場景中都離不開“定時器”,就像一個定好時間規(guī)則的鬧鐘,它會在指定時間觸發(fā),執(zhí)行我們想要定義的調(diào)度任務(wù)。那么我們今天就來數(shù)一下,那些年我們用過的“定時調(diào)度”。
1. job (oracle)從剛工作就一直使用oracle數(shù)據(jù)庫,最早接觸的定時任務(wù)就是oracle數(shù)據(jù)庫的job。job有定時執(zhí)行的功能,可以在指定的時間點(diǎn)或每天的某個時間點(diǎn)自行執(zhí)行任務(wù)。 而且oracle重新啟動后,job會繼續(xù)運(yùn)行,不用重新啟動。
而且job的機(jī)制非常完備,可以查詢相關(guān)的表或視圖,查詢job的定時規(guī)則和執(zhí)行情況。缺點(diǎn)是作為oracle數(shù)據(jù)庫層面的工具,自定義功能擴(kuò)展,二次開發(fā)的難度比較大。
1.1 創(chuàng)建job
DECLARE job NUMBER; BEGIN sys.dbms_job.submit(job => job, what => "prc_name;", --執(zhí)行的存儲過程的名字 next_date => to_date("22-11-2013 09:09:41", "dd-mm-yyyy hh24:mi:ss"), --下一次執(zhí)行時間 interval =>"sysdate+1/24"); --每天24小時,即每小時運(yùn)行prc_name過程一次 END; -- job參數(shù)是輸出參數(shù),由submit()過程返回的binary_ineger,這個值用來唯一標(biāo)識一個工作。一般定義一個變量接收,可以去user_jobs視圖查詢job值。 -- what參數(shù)是將被執(zhí)行的PL/SQL代碼塊,存儲過程名稱等。 -- next_date參數(shù)指識何時將運(yùn)行這個工作。 -- interval參數(shù)何時這個工作將被重執(zhí)行
1.2 刪除job
DECLARE BEGIN dbms_job.remove(1093); -- 1093為當(dāng)前需要刪除的 job 值 COMMIT; END;
1.3 查詢job
-- 查詢當(dāng)前用戶的job select * from user_jobs; -- 查詢所有job select * from dba_jobs; -- 查詢所有運(yùn)行中的job select * from dba_jobs_running;2. crontab (linux)
crond 是linux下用來周期性的執(zhí)行某種任務(wù)或等待處理某些事件的一個守護(hù)進(jìn)程,與windows下的計劃任務(wù)類似,當(dāng)安裝完成操作系統(tǒng)后,默認(rèn)會安裝此服務(wù) 工具,并且會自動啟動crond進(jìn)程,crond進(jìn)程每分鐘會定期檢查是否有要執(zhí)行的任務(wù),如果有要執(zhí)行的任務(wù),則自動執(zhí)行該任務(wù)。
cron是服務(wù)名稱,crond是后臺進(jìn)程,crontab則是定制好的計劃任務(wù)表。大部分linux系統(tǒng)默認(rèn)都安裝了cron,可以檢查一下。
-- 檢查Crontab工具是否安裝 crontab -l -- 檢查crond服務(wù)是否啟動 service crond status -- centos安裝 yum install vixie-cron yum install crontabs
crontab基本操作命令
-- 列出某個用戶cron服務(wù)的詳細(xì)內(nèi)容 crontab -l -- 編輯某個用戶的cron服務(wù) crontab -e
crontab表達(dá)式格式
{minute} {hour} {day-of-month} {month} {day-of-week} {full-path-to-shell-script} minute: 區(qū)間為 0 – 59 hour: 區(qū)間為0 – 23 day-of-month: 區(qū)間為0 – 31 month: 區(qū)間為1 – 12. 1 是1月. 12是12月 Day-of-week: 區(qū)間為0 – 7. 周日可以是0或7. 在以上各個字段中,還可以使用以下特殊字符: 星號(*):代表所有可能的值,例如month字段如果是星號,則表示在滿足其它字段的制約條件后每月都執(zhí)行該命令操作。 逗號(,):可以用逗號隔開的值指定一個列表范圍,例如,“1,2,5,7,8,9” 中杠(-):可以用整數(shù)之間的中杠表示一個整數(shù)范圍,例如“2-6”表示“2,3,4,5,6” 正斜線(/):可以用正斜線指定時間的間隔頻率,例如“0-23/2”表示每兩小時執(zhí)行一次。同時正斜線可以和星號一起使用,例如*/10,如果用在minute字段,表示每十分鐘執(zhí)行一次。
推薦一個crontab表達(dá)式的校驗(yàn)網(wǎng)站(https://tool.lu/crontab/)
3. Timer和ScheduledExecutorService (java)Timer是jdk中提供的一個定時器工具,使用的時候會在主線程之外起一個多帶帶的線程執(zhí)行指定的計劃任務(wù),可以指定執(zhí)行一次或者反復(fù)執(zhí)行多次。
//只執(zhí)行一次 public void schedule(TimerTask task, long delay); public void schedule(TimerTask task, Date time); //循環(huán)執(zhí)行 // 在循環(huán)執(zhí)行類別中根據(jù)循環(huán)時間間隔又可以分為兩類 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是一個實(shí)現(xiàn)了Runnable接口的抽象類,代表一個可以被Timer執(zhí)行的任務(wù)。TimerTask類是一個抽象類,由Timer 安排為一次執(zhí)行或重復(fù)執(zhí)行的任務(wù)。它有一個抽象方法run()方法,該方法用于執(zhí)行相應(yīng)計時器任務(wù)要執(zhí)行的操作。因此每一個具體的任務(wù)類都必須繼承TimerTask,然后重寫run()方法。
另外它還有兩個非抽象的方法
-- 取消此計時器任務(wù) boolean cancel() -- 返回此任務(wù)最近實(shí)際執(zhí)行的安排執(zhí)行時間 long scheduledExecutionTime()
當(dāng)然,一般使用Timer的比較少,因?yàn)樗娜秉c(diǎn)比較明顯:
單線程,當(dāng)多個timer同時運(yùn)行時,會等上一個執(zhí)行完成,再執(zhí)行下一個。
Timer線程是不會捕獲異常的,如果TimerTask拋出的了未檢查異常則會導(dǎo)致Timer線程終止。
所以一般使用ScheduledExecutorService替代Timer。
ScheduledExecutorService:也是jdk自帶的一個基于線程池設(shè)計的定時任務(wù)類。其每個調(diào)度任務(wù)都會分配到線程池中的一個線程執(zhí)行,所以其任務(wù)是并發(fā)執(zhí)行的,互不影響。
Timer和ScheduledExecutorService都是屬于jdk層面上實(shí)現(xiàn)定時調(diào)度的類,功能還不足以讓我們滿意,那么現(xiàn)在介紹一個比較完善的定時調(diào)度工具 - SpringTask,是Spring提供的,支持注解和配置文件形式,支持crontab表達(dá)式,使用簡單但功能強(qiáng)大。我個人非常喜歡SpringTask,僅僅是因?yàn)橹С謈rontab表達(dá)式。
在springboot里面使用方式非常簡單:
啟動類添加開啟定時調(diào)度的注解 @EnableScheduling
在需要定時執(zhí)行的方法上,增加注解 @Scheduled(cron ="crontab表達(dá)式")
默認(rèn)的簡單的使用步驟只有以上兩步,但是SpringTask的默認(rèn)使用方式也有一些不足:
默認(rèn)線程池的poolsize為1,可以理解為Timer類似的單線程模式。
無法動態(tài)修改crontab表達(dá)式,修改完只能重新部署后,才能生效。
問題1的解決方式,可以通過自定義 TaskExecutor來修改當(dāng)前的線程池。問題2,則可以直接使用 threadPoolTaskScheduler類實(shí)現(xiàn)自定義的定時調(diào)度規(guī)則。
附解決兩個問題的源碼 TaskTimer.class
@Component public class TaskTimer { @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; @Autowired private TaskRepo taskRepo; /** * ***定時引擎*** * * 實(shí)例化一個線程池任務(wù)調(diào)度類 * 默認(rèn) ThreadPoolTaskScheduler 的 poolSize 為1,類似于newSingleThreadExecutor 單線程模式,只能執(zhí)行完一個調(diào)度,再執(zhí)行其他調(diào)度 * 需要自定義擴(kuò)展poolSize,允許一定程度的 多線程并行場景 * * @return */ @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(100); threadPoolTaskScheduler.setThreadNamePrefix("Thread - "); threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true); threadPoolTaskScheduler.setAwaitTerminationSeconds(60); return threadPoolTaskScheduler; } /** * 啟動定時調(diào)度 * @param taskCode * @param cron * @param runnable * @return */ public boolean start(String taskCode, String cron,Runnable runnable){ ScheduledFuture> currentFuture=taskRepo.findTask(taskCode); //已存在的調(diào)度,無法再創(chuàng)建 if(currentFuture!=null){ throw new RuntimeException("調(diào)度""+taskCode+""已存在,無法再創(chuàng)建"); } //創(chuàng)建新的調(diào)度,并加入 taskMap currentFuture = threadPoolTaskScheduler.schedule(runnable,new CronTrigger(cron)); if (currentFuture!=null){ this.taskRepo.addTask(taskCode,currentFuture); return true; } throw new RuntimeException("任務(wù)啟動失?。。。?); } /** * 暫停定時調(diào)度 * @param taskCode * @return */ public boolean stop(String taskCode) { //taskId 不存在的,無法停止,只能修改 ScheduledFuture> currentFuture=this.taskRepo.findTask(taskCode); if(currentFuture!=null){ return currentFuture.cancel(true); } return true; } /** * 刪除定時調(diào)度 * @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; } /** * 修改定時調(diào)度 * @param taskCode * @param cron * @param runnable * @return */ public boolean update(String taskCode,String cron,Runnable runnable){ ScheduledFuture> currentFuture=this.taskRepo.findTask(taskCode); //已存在的定時調(diào)度,先停止,再新增,并更新 新的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; } }5. Quartz (其他產(chǎn)品)
Quartz是一個完全由 Java 編寫的開源作業(yè)調(diào)度框架,為在 Java 應(yīng)用程序中進(jìn)行作業(yè)調(diào)度提供了簡單卻強(qiáng)大的機(jī)制。它是一個功能強(qiáng)大、十分成熟的重量級產(chǎn)品,還支持負(fù)載均衡,實(shí)現(xiàn)分布式調(diào)度。
不過,對于Quartz的安裝你要多花點(diǎn)功夫了,從數(shù)據(jù)庫要建哪些表,到應(yīng)用程序該如何部署。對于這樣一個龐大的產(chǎn)品,本篇文章就不附上它的使用說明書了。
參考文檔SpringBoot中并發(fā)定時任務(wù)的實(shí)現(xiàn)、動態(tài)定時任務(wù)的實(shí)現(xiàn)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74379.html
摘要:類是一個抽象類,由安排為一次執(zhí)行或重復(fù)執(zhí)行的任務(wù)。也是自帶的一個基于線程池設(shè)計的定時任務(wù)類。問題,則可以直接使用類實(shí)現(xiàn)自定義的定時調(diào)度規(guī)則。 定時調(diào)度作為后端開發(fā)人員,我們總會遇到這樣的業(yè)務(wù)場景:每周同步一批數(shù)據(jù);每半個小時檢查一遍服務(wù)器運(yùn)行狀況;每天早上八點(diǎn)給用戶發(fā)送一份包含今日待辦事項的郵件,等等。 這些場景中都離不開定時器,就像一個定好時間規(guī)則的鬧鐘,它會在指定時間觸發(fā),執(zhí)行我們...
摘要:本文里提到的所有都是基于版本的,這使得我又一次被鄙視了使用的程序猿鄙視使用的程序猿。因此這些事物碼本身也是可以通過進(jìn)行增強(qiáng)的。 在Google上根據(jù)關(guān)鍵字程序員鄙視鏈搜索,會得到68多萬條結(jié)果。 showImg(https://segmentfault.com/img/remote/1460000014000687); 玲瑯滿目的搜索結(jié)果里是眾多不同維度劃分的鄙視鏈。 其中有一個維度,...
閱讀 2480·2021-10-12 10:11
閱讀 1229·2021-10-11 10:58
閱讀 3273·2019-08-30 15:54
閱讀 712·2019-08-30 13:59
閱讀 680·2019-08-29 13:07
閱讀 1407·2019-08-26 11:55
閱讀 2142·2019-08-26 10:44
閱讀 2642·2019-08-23 18:25