使用quartz集群開發(fā)定時任務整理

?????? 在微服務環(huán)境下挠将,定時任務也需要獨立為一個服務。這里使用spring+quartz搭建定時任務開發(fā)環(huán)境。

?????? 在Config加載quartz.properties配置文件時砸烦,本地環(huán)境因為資源文件我們都存放在項目resource下冗尤,可使用ClassPathResource去拿到資源文件听盖。可是在集成裂七、測試皆看、生產(chǎn)環(huán)境下,一般會把配置文件都拿出來統(tǒng)一放在項目外的一個文件中背零,而ClassPathResource會從項目根目錄下開始查找資源腰吟,于是會拿不到項目外的quartz.properties,導致定時任務執(zhí)行可能會與預期結(jié)果不一致徙瓶,尤其是在集群環(huán)境中毛雇。讀取資源文件可采用PathResouce讀取配置文件的絕對路徑录语。

?????? 我們將調(diào)度信息存儲在mysql中,按照quartz規(guī)范在數(shù)據(jù)庫建立QRTZ_JOB_DETAILS,QRTZ_TRIGGERS等共11張表禾乘。建表sql可在quartz發(fā)型包中/docs/dbTables里看到澎埠。配置

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX?

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

org.quartz.jobStore.tablePrefix = QRTZ_?

quartz.properties有幾個配置可以注意下。集群的配置

org.quartz.jobStore.isClustered = true 開啟集群特性

org.quartz.jobStore.clusterCheckinInterval = 20000 設(shè)置Scheduler實例節(jié)點檢測頻率始藕,節(jié)點出現(xiàn)問題會被發(fā)現(xiàn)

org.quartz.jobStore.misfireThreshold = 60000 設(shè)置定時任務失火閾值蒲稳,當前時間超過原定執(zhí)行時間若是在閾值之內(nèi),就可以執(zhí)行

?????? 新建一個任務表用于存放我們配置要執(zhí)行的任務信息quartz_config表伍派,任務(組)名江耀,觸發(fā)器(組)名,執(zhí)行類诉植,cron表達式等祥国,可以在前端頁面對任務管理。在進行周期性任務狀態(tài)變化檢測時晾腔,需要取quartz_config內(nèi)的值來進行判斷舌稀。這里使用實現(xiàn)SchedulingConfigurer接口來完成動態(tài)定時任務

? ? /**

? ? * 執(zhí)行定時任務.

? ? */

? ? @Override

? ? public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

? ? ? ? taskRegistrar.addTriggerTask(

? ? ? ? ? ? ? ? //1.添加任務內(nèi)容(Runnable)

? ? ? ? ? ? ? ? () -> quartzManager.chickJobs(),

? ? ? ? ? ? ? ? //2.設(shè)置執(zhí)行周期(Trigger)

? ? ? ? ? ? ? ? triggerContext -> {

? ? ? ? ? ? ? ? ? ? //2.1 從數(shù)據(jù)庫獲取執(zhí)行周期

? ? ? ? ? ? ? ? ? ? String cron = env.getProperty("job.cron");

? ? ? ? ? ? ? ? ? ? //2.2 合法性校驗.

? ? ? ? ? ? ? ? ? ? if (StringUtils.isEmpty(cron)) {

? ? ? ? ? ? ? ? ? ? ? ? cron="0/5 * * * * ?";

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? //2.3 返回執(zhí)行周期(Date)

? ? ? ? ? ? ? ? ? ? return new CronTrigger(cron).nextExecutionTime(triggerContext);

? ? ? ? ? ? ? ? }

? ? ? ? );

? ? }

?????? 如圖,配置每分鐘檢測一次任務狀態(tài)變化灼擂,檢測功能在quartzManager.chickJobs()中實現(xiàn)壁查。目前我們設(shè)置Job的狀態(tài)有啟動,暫停剔应,刪除睡腿,刪除就邏輯刪除,頁面不展示峻贮,在quartz_config中以status字段來標示席怪。我們點擊啟動任務后,做的操作就將status置為1,此時雖然顯示已啟動纤控,可實際上是還未注冊實例的挂捻,然后等待下一次任務狀態(tài)檢測,取到status為1的任務數(shù)據(jù)嚼黔,然后使用scheduler.checkExists()檢測當前scheduler實例是否已經(jīng)存在該job,如果已經(jīng)存在细层,則獲取當前job實例的Cron表達式,判斷任務的觸發(fā)時間是否有變化唬涧,若有,則更新觸發(fā)器

? ? ? ? ? ? ? ? // 觸發(fā)器

? ? ? ? ? ? ? ? TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();

? ? ? ? ? ? ? ? // 觸發(fā)器名,觸發(fā)器組

? ? ? ? ? ? ? ? triggerBuilder.withIdentity(triggerName, triggerGroupName);

? ? ? ? ? ? ? ? triggerBuilder.startNow();

? ? ? ? ? ? ? ? // 觸發(fā)器時間設(shè)定

? ? ? ? ? ? ? ? triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron).withMisfireHandlingInstructionDoNothing());

? ? ? ? ? ? ? ? // 創(chuàng)建Trigger對象

? ? ? ? ? ? ? ? trigger = (CronTrigger) triggerBuilder.build();

? ? ? ? ? ? ? ? // 方式一 :修改一個任務的觸發(fā)時間

? ? ? ? ? ? ? ? sched.rescheduleJob(triggerKey, trigger);

否則添加一個job實例

?????? 動態(tài)檢測時對暫停和刪除的Job的處理邏輯是盛撑,先取出quartz_config中status不等于1的數(shù)據(jù)碎节,然后判斷scheduler中是否存在該Job,存在就證明該job還是已注冊的狀態(tài)抵卫,就將該job從調(diào)度器中移除狮荔。

? ? ? ? ? Scheduler sched = schedulerFactory.getScheduler();

? ? ? ? ? ? TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);

? ? ? ? ? ? log.info("===============remove Job:{}===============",jobName);

? ? ? ? ? ? sched.pauseTrigger(triggerKey);// 停止觸發(fā)器

? ? ? ? ? ? sched.unscheduleJob(triggerKey);// 移除觸發(fā)器

? ? ? ? ? ? sched.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 刪除任務

?????? 在集群多節(jié)點時胎撇,動態(tài)檢測管理job狀態(tài),還需要做進一步控制殖氏,之前就因為沒做控制晚树,在本地時,就一臺服務器雅采,一直運行無誤爵憎,找原因找了許久。假設(shè)集群中的兩臺服務器同時執(zhí)行了任務檢測邏輯婚瓜,此時有一個任務點擊啟動(status=1)宝鼓,正在等待檢測邏輯開始運行添加進實例(即insert進JOB_DETAILS,TRIGGERS等表),兩臺服務器同時拿到了這條待添加的job巴刻,必定有一臺服務器先將任務實例持久化到數(shù)據(jù)庫愚铡,另一臺服務器在執(zhí)行sched.scheduleJob(jobDetail,trigger)時,執(zhí)行到底層storeJob方法時胡陪,就會報出ObjectAlreadyExistsException異常

if (existingJob) {

? ? ? ? ? ? ? ? if (!replaceExisting) {

? ? ? ? ? ? ? ? ? ? throw new ObjectAlreadyExistsException(newJob);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? this.getDelegate().updateJobDetail(conn, newJob);

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? this.getDelegate().insertJobDetail(conn, newJob);

? ? ? ? ? ? }

?????? 我們的處理方法是沥寥,在quartz_config表中新增一個process_status字段,來標示當前任務處理狀態(tài)柠座,1為待處理营曼,2為處理中,3為處理完成愚隧,保證同時只有一個節(jié)點能執(zhí)行該添加修改操作蒂阱。quartz_config數(shù)據(jù)初始化改狀態(tài)為1,暫涂裉粒或修改Cron录煤,都會講process_status置為1,因為它是發(fā)生變化待檢測邏輯處理的荞胡。這樣的話妈踊,如上例,兩節(jié)點同時執(zhí)行下來泪漂,先到的一個會將process_status更新為2(處理中)廊营。

? ? ? UPDATE plms_quartz_config SET process_status = #{processStatus,jdbcType=VARCHAR} WHERE job_name = #{jobName,jdbcType=VARCHAR} AND process_status = '1'

更新成功則返回result = 1,只有result = 1的時候才會執(zhí)行接下來的添加修改操作萝勤。當前處理狀態(tài)已經(jīng)從1變成2了露筒,因為where條件的限制,另一節(jié)點到此已經(jīng)更新不到改狀態(tài)了敌卓,所以返回result = 0慎式,就不會再一次addJob或addTirgger,避免對象已存在異常。

?????? 有些情況下瘪吏,到了指定時間才觸發(fā)某個任務的執(zhí)行可能滿足不了需求癣防,我們需要能手動觸發(fā)一個任務立即執(zhí)行來完成有些特殊情況。立即觸發(fā)一個任務掌眠,我判斷了當前正在執(zhí)行中的任務不能立即執(zhí)行蕾盯。在任務執(zhí)行中,該任務真正觸發(fā)時間到了蓝丙,需要執(zhí)行级遭,會導致任務重復執(zhí)行,job類上因加上@DisallowConcurrentExecution防止任務重復執(zhí)行(集群都需要)迅腔。解決重復執(zhí)行了装畅,可是任務可能會因為misfire失火機制在空閑時間或者下個輪詢周期補償此次的錯失執(zhí)行〔琢遥看業(yè)務需要掠兄,配置失火策略,此處防止只執(zhí)行一次的任務多次執(zhí)行锌雀,我選擇了失火之后忽略該任務不做補償執(zhí)行,實現(xiàn)方法是在rescheduleJob或scheduleJob之前設(shè)置觸發(fā)器時蚂夕,如下:

riggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron).withMisfireHandlingInstructionDoNothing());

此處手動觸發(fā)立即執(zhí)行任務

//執(zhí)行任務

? ? ? ? ? ? ? ? JobDetail job = JobBuilder.newJob((Class<? extends Job>) Class.forName(performJobReqDTO.getJobClassName())).withIdentity(jobKey).storeDurably().build();

? ? ? ? ? ? ? ? scheduler.addJob(job, true);

? ? ? ? ? ? ? ? scheduler.triggerJob(jobKey);

? ? ? ? ? ? ? ? log.info("job:{}任務已手動觸發(fā)",JSON.toJSONString(jobKey));

?????? 又遇到了個坑。此處立即執(zhí)行任務triggerJob觸發(fā)后腋逆,會在JOB_DETAILS婿牍,TRIGGERS,SIMPLE_TRIGGERS表存入數(shù)據(jù),觸發(fā)完后會刪除TRIGGER的數(shù)據(jù)惩歉,如若此時該任務正好處于剛點擊了啟動但是還未注冊的情況等脂,或者點立即執(zhí)行馬上又點擊啟動,因為立即執(zhí)行導致JobDetail已經(jīng)有該job的數(shù)據(jù)撑蚌,任務狀態(tài)檢測的時候就不會將該任務新注冊進去上遥,導致只有jobDetail,但缺失觸發(fā)器争涌,該任務就永遠不會執(zhí)行粉楚。處理方法為,若任務處理狀態(tài)為非處理完成亮垫,在立即執(zhí)行觸發(fā)后模软,清除該任務CronTrigger,SimpleTrigger,Trigger,JobDetail,當動態(tài)檢測執(zhí)行時,就能正常注冊任務觸發(fā)器饮潦。

?????? 又有個坑燃异,立即執(zhí)行觸發(fā)(scheduler.triggerJob(jobKey))后刪除那幾張表,可能會導致job實例不執(zhí)行害晦,任務觸發(fā)成功特铝,但是實際任務沒跑暑中,懷疑是triggerJob(jobKey)方法內(nèi)部執(zhí)行壹瘟,開啟一個線程后還需要去查表拿數(shù)據(jù)鲫剿,清楚太快導致沒拿到數(shù)據(jù),就沒跑成功稻轨,具體原因沒仔細研究灵莲,我在此處的解決方法是刪除之前讓線程睡一秒,確保任務能正常執(zhí)行殴俱。

?????? 本次也是初次對集群輪詢環(huán)境做這些大致的構(gòu)建政冻,做一下遇到的問題記錄筆記。當然以上處理方式還有很多更好更嚴謹?shù)奶幚矸绞接写齼?yōu)化线欲。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末明场,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子李丰,更是在濱河造成了極大的恐慌苦锨,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趴泌,死亡現(xiàn)場離奇詭異舟舒,居然都是意外死亡,警方通過查閱死者的電腦和手機嗜憔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門拖叙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來此蜈,“玉大人,你說我怎么就攤上這事×嫖ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵淑趾,是天一觀的道長卿叽。 經(jīng)常有香客問我,道長滋早,這世上最難降的妖魔是什么榄审? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮杆麸,結(jié)果婚禮上搁进,老公的妹妹穿的比我還像新娘。我一直安慰自己昔头,他們只是感情好饼问,可當我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著揭斧,像睡著了一般莱革。 火紅的嫁衣襯著肌膚如雪峻堰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天盅视,我揣著相機與錄音捐名,去河邊找鬼。 笑死闹击,一個胖子當著我的面吹牛镶蹋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赏半,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼贺归,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了断箫?” 一聲冷哼從身側(cè)響起拂酣,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仲义,沒想到半個月后婶熬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡光坝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年尸诽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盯另。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡性含,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸳惯,到底是詐尸還是另有隱情商蕴,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布芝发,位于F島的核電站绪商,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏辅鲸。R本人自食惡果不足惜格郁,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望独悴。 院中可真熱鬧例书,春花似錦、人聲如沸刻炒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坟奥。三九已至树瞭,卻和暖如春拇厢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背晒喷。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工孝偎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厨埋。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓邪媳,卻偏偏與公主長得像捐顷,于是被迫代替她去往敵國和親荡陷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內(nèi)容