前言
項目中總要寫那麼幾個定時任務來處理一些事情。一些簡單的定時任務使用Spring自帶的定時任務就能完成。但是如果需要大量的定時任務的話要怎麼才能統一管理呢?
本文介紹Quartz分布式排程架構。
介紹
Quartz介紹
Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,是完全由java開發的一個開源的任務日程管理系統。目前是 Terracotta 旗下的一個項目。官網位址 http://www.quartz-scheduler.org/ 可以 下載下傳 Quartz 的釋出版本及其源代碼。
特點
- 內建友善(完全使用Java編寫)
- 無需依賴可叢集部署也可單機運作
- 可以通過JVM獨立運作
Job
建立一個任務隻需要實作Job接口即可
觸發器
- 可以通過 Calendar 執行(排除節假日)
- 指定某個時間無線循環執行 比如每五分鐘執行一次
- 固定時間執行 例如每周周一上午十點執行
一般情況使用SimpleTrigger,和CronTrigger,這些觸發器實作了Trigger接口。或者 ScheduleBuilder 子類 SimpleScheduleBuilder和CronScheduleBuilder。
對于簡單的時間來說,比如每天執行幾次,使用SimpleTrigger。對于複雜的時間表達式來說,比如每個月15日上午幾點幾分,使用CronTrigger以及CromExpression 類。
注意 :一個job可以被多個Trigger 綁定,但是一個Trigger隻能綁定一個job
存儲
有兩種存儲方式 RAMJobStore和 JDBCJobStore 。
RAMJobStore不需要外部資料庫排程資訊存儲在JVM記憶體中 是以,當應用程式停止運作時,所有排程資訊将被丢失存儲多少個Job和Trigger也會受到限制。
JDBCJobStore 支援叢集所有觸發器和job都存儲在資料庫中無論伺服器停止和重新開機都可以恢複任務同時支援事務處理。
實戰
準備
上面簡單的介紹了一下Quartz,然後現在開始實戰,本文使用SpringBoot整合。
項目位址:https://gitee.com/lqlm/toolsList_lqcoder
首先建立資料庫表因為太多了就不放在文章中了可以去官方網站下載下傳,也可以用我的下載下傳位址下載下傳
位址:https://lqcoder.com/quartz.sql
建立完成之後:
Table Name | Description |
---|---|
QRTZ_CALENDARS | 存儲Quartz的Calendar資訊 |
QRTZ_CRON_TRIGGERS | 存儲CronTrigger,包括Cron表達式和時區資訊 |
QRTZ_FIRED_TRIGGERS | 存儲與已觸發的Trigger相關的狀态資訊,以及相聯Job的執行資訊 |
QRTZ_PAUSED_TRIGGER_GRPS | 存儲已暫停的Trigger組的資訊 |
QRTZ_SCHEDULER_STATE | 存儲少量的有關Scheduler的狀态資訊,和别的Scheduler執行個體 |
QRTZ_LOCKS | 存儲程式的悲觀鎖的資訊 |
QRTZ_JOB_DETAILS | 存儲每一個已配置的Job的詳細資訊 |
QRTZ_JOB_LISTENERS | 存儲有關已配置的JobListener的資訊 |
QRTZ_SIMPLE_TRIGGERS | 存儲簡單的Trigger,包括重複次數、間隔、以及已觸的次數 |
QRTZ_BLOG_TRIGGERS | Trigger作為Blob類型存儲 |
QRTZ_TRIGGER_LISTENERS | 存儲已配置的TriggerListener的資訊 |
QRTZ_TRIGGERS | 存儲已配置的Trigger的資訊 |
本文統一使用Cron方式來建立。
注意:cron方式需要用到的4張資料表:qrtz_triggers,qrtz_cron_triggers,qrtz_fired_triggers,qrtz_job_details。
整合項目
建立一個SpringBoot項目然後加入quartz依賴,同時也要加入c3p0的依賴因為quartz使用的資料庫是和項目分開的。
同時在resources下建立quartz.properties檔案内容
然後建立一個Job類
建立一個工具類然後進行定時任務的增删改
首先建立一個排程工廠
添加定時任務
public static void addJob(String jobName, String jobGroupName,
String triggerName, String triggerGroupName, Class jobClass, String cron) {
try {
Scheduler sched = schedulerFactory.getScheduler();
// 任務名,任務組,任務執行類
// Trigger.TriggerState state = sched.getTriggerState();
JobDetail jobDetail= JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
// 觸發器
TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger();// 觸發器名,觸發器組
triggerBuilder.withIdentity(triggerName, triggerGroupName);
triggerBuilder.startNow();// 觸發器時間設定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));// 建立Trigger對象
CronTrigger trigger = (CronTrigger) triggerBuilder.build();// 排程容器設定JobDetail和Trigger
sched.scheduleJob(jobDetail, trigger);// 啟動if (!sched.isShutdown()) {
sched.start();
}
} catch (Exception e) {throw new RuntimeException(e);
}
}
建立流程
通過工廠擷取 Scheduler對象
Scheduler sched = schedulerFactory.getScheduler();
設定Job的實作類和一些靜态資訊
建構觸發器
// 觸發器
TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger();
// 觸發器名,觸發器組
triggerBuilder.withIdentity(triggerName, triggerGroupName);
triggerBuilder.startNow();
// 觸發器時間設定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
// 建立Trigger對象
CronTrigger trigger = (CronTrigger) triggerBuilder.build();
然後把Job和觸發器都設定到Scheduler對象中
啟動
運作
因為使用的是SpringBoot項目是以就直接在啟動類加入添加定時任務
參數分别為:JobName JsobgropName 中間省略 實作類,任務執行時間
然後檢視輸出日志:
可以看到已經在執行了,現在我們去看一下資料庫中的資料要檢視的表有qrtz_triggers,qrtz_cron_triggers,qrtz_fired_triggers,qrtz_job_details。
qrtz_job_details:
已經存在
現在把項目停止然後在重新啟動會發生什麼?
發現抛出了異常,因為我們已經添加過這個定時任務了是以重複添加是行不通的。
這時候我們直接啟動即可。
同樣封裝啟動方法
啟動所有定時任務非常簡單直接擷取Scheduler對象然後start即可。
修改定時任務
修改定時任務同樣需要擷取Scheduler對象,和添加流程基本一緻,隻不過最後不是調用的scheduleJob()而是調用的rescheduleJob()方法.有兩種方式都需要指定定時器名稱
- 第一種是調用rescheduleJob()直接修改
- 第二種是先删除然後在新增
/**
* @Description: 修改一個任務的觸發時間
*
* @param jobName
* @param jobGroupName
* @param triggerName 觸發器名
* @param triggerGroupName 觸發器組名
* @param cron 時間設定,參考quartz說明文檔
*/
public static void modifyJobTime(String jobName,
String jobGroupName, String triggerName, String triggerGroupName, String cron) {
try {
Scheduler sched = schedulerFactory.getScheduler();
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
if (trigger == null) {
return;
}
String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(cron)) {
/** 方式一 :調用 rescheduleJob 開始 */
// 觸發器
TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger();// 觸發器名,觸發器組
triggerBuilder.withIdentity(triggerName, triggerGroupName);
triggerBuilder.startNow();// 觸發器時間設定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));// 建立Trigger對象
trigger = (CronTrigger) triggerBuilder.build();// 方式一 :修改一個任務的觸發時間
sched.rescheduleJob(triggerKey, trigger);/** 方式一 :調用 rescheduleJob 結束 *//** 方式二:先删除,然後在建立一個新的Job *///JobDetail jobDetail = sched.getJobDetail(JobKey.jobKey(jobName, jobGroupName));//Class extends Job> jobClass = jobDetail.getJobClass();//removeJob(jobName, jobGroupName, triggerName, triggerGroupName);//addJob(jobName, jobGroupName, triggerName, triggerGroupName, jobClass, cron);/** 方式二 :先删除,然後在建立一個新的Job */
}
} catch (Exception e) {throw new RuntimeException(e);
}
}
删除任務
删除定時任務在修改的時候已經有執行個體.注意都需要指定任務名稱 任務分組和觸發器名稱觸發器分組
傳遞參數
現在還有一個問題就是我想把參數傳遞到Job實作類裡面咋整?
在添加定時任務時,建立JobDetail的時候有一個setJobData()方法參數為JobDataMap,看下JobBuilder源碼
可以看到JobBuilder提供了setJobData方法傳遞的參數為JobDataMap是Map類型.
在建立定時任務的時候可以:
然後在Job實作類方法中直接取
JobDataMap可以直接當作Map進行操作.
單個參數可以使用usingJobData()來添加,參數為K V 取值方法一緻,同時參數也是持久化到資料庫的
如果需要查詢管理的話可以直接查詢資料庫
原了解析
上面簡單的介紹了一下怎麼使用,那麼你一定對它是怎麼運作的感興趣.接下來就分析一下Quartz到底是怎麼實作的
注意到上面增删改都要先通過schedulerFactory工廠(工廠模式)來先擷取Scheduler執行個體,現在就從第一步開始分析
本文就簡單分析一下Scheduler工廠和添加定時任務這兩步驟.
Scheduler工廠
添加定時任務
- End -
Redis是如何實作點贊、取消點贊的?
阿裡巴巴為什麼能抗住90秒100億?看完這篇你就明白了!
超詳細:如何設計出健壯的秒殺系統?
我是如何用 Redis 做實時訂閱推送的?