Quartz
Quartz 是 OpenSymphony 開源組織在 Job scheduling 領域又一個開源項目,它可以與 J2EE 與 J2SE 應用程序相結合也可以單獨使用。Quartz 可以用來創建簡單或爲運行十個,百個,甚至是好幾萬個 Jobs 這樣複雜的程序。Jobs 可以做成標準的 Java 組件或 EJBs。Quartz 的最新版本爲 Quartz 2.3.0。
1.1 Quartz 調用流程
- JOB: 定義自己的任務
- JobDetail: 封裝 JOB 對象的
- 調度器: 管理全部的任務(Scheduler)
- 觸發器: 開啓新的線程執行任務(jobDetail)
1.2 Quartz組件說明
1.2.1 Scheduler – 調度器
Scheduler 被用來對 Trigger 和 Job 進行管理。Trigger 和 JobDetail 可以註冊到Scheduler 中,兩者在 Scheduler 中都擁有自己的唯一的組和名稱用來進行彼此的區分,Scheduler 可以通過組名或者名稱來對 Trigger 和 JobDetail 來進行管理。一個 Trigger只能對應一個 Job,但是一個 Job 可以對應多個 Trigger。每個 Scheduler 都包含一個SchedulerContext,用來保存 Scheduler 的上下文。Job 和 Trigger 都可以獲取SchedulerContext 中的信息。
Scheduler 包含兩個重要的組件,JobStore 和 ThreadPool。JobStore 用來存儲運行時信息,包括 Trigger,Schduler,JobDetail,業務鎖等。它有多種實現 RAMJob(內存實現),JobStoreTX(JDBC,事務由 Quartz 管理)等。ThreadPool 就是線程池,Quartz有自己的線程池實現。所有任務的都會由線程池執行。
Scheduler 是由 SchdulerFactory 創建,它有兩個實現:DirectSchedulerFactory和 StdSchdulerFactory。前者可以用來在代碼裏定製你自己的 Schduler 參數。後者是直接讀取 classpath 下的 quartz.properties(不存在就都使用默認值)配置來實例化Schduler。通常來講,我們使用 StdSchdulerFactory 也就足夠了。
1.2.2 Trigger – 觸發器
Trigger 是用來定義 Job 的執行規則,主要有四種觸發器,其中 SimpleTrigger 和CronTrigger 觸發器用的最多。
SimpleTrigger:從某一個時間開始,以一定的時間間隔來執行任務。它主要有兩個屬性,repeatInterval 重複的時間間隔;repeatCount 重複的次數,實際上執行的次數是 n+1,因爲在 startTime 的時候會執行一次。
CronTrigger:適合於複雜的任務,使用 cron 表達式來定義執行規則。
CalendarIntervalTrigger:類似於 SimpleTrigger,指定從某一個時間開始,以一定的時間間隔執行的任務。 但是 CalendarIntervalTrigger 執行任務的時間間隔比SimpleTrigger 要豐富,它支持的間隔單位有秒,分鐘,小時,天,月,年,星期。相較於 SimpleTrigger 有兩個優勢:1、更方便,比如每隔 1 小時執行,你不用自己去計算 1小時等於多少毫秒。 2、支持不是固定長度的間隔,比如間隔爲月和年。但劣勢是精度只能到秒。它的主要兩個屬性,interval執行間隔;intervalUnit 執行間隔的單位(秒,分鐘,小時,天,月,年,星期)。
DailyTimeIntervalTrigger:指定每天的某個時間段內,以一定的時間間隔執行任務。並且它可以支持指定星期。它適合的任務類似於:指定每天 9:00 至 18:00 ,每隔70 秒執行一次,並且只要週一至週五執行。它的屬性有 startTimeOfDay 每天開始時間;endTimeOfDay 每天結束時間;daysOfWeek 需要執行的星期;interval 執行間隔;intervalUnit 執行間隔的單位(秒,分鐘,小時,天,月,年,星期);repeatCount 重複次數。
所有的 trigger 都包含了 StartTime 和 endTIme 這兩個屬性,用來指定 Trigger被觸發的時間區間。
所有的 trigger 都可以設置 MisFire 策略,該策略是對於由於系統奔潰或者任務時間過長等因導致trigger在應該觸發的時間點沒有觸發,並且超過了misfireThreshold設置的時間(默認是一分鐘,沒有超過就立即執行)就算 misfire 了,這個時候就該設置如何應對這種變化了。激活失敗指令(Misfire Instructions)是觸發器的一個重要屬性,它指定了 misfire 發生時調度器應當如何處理。所有類型的觸發器都有一個默認的指令,叫做Trigger.MISFIRE_INSTRUCTION_SMART_POLICY,但是這個這個“聰明策略”對於不同類型的觸發器其具體行爲是不同的。對於 SimpleTrigger,這個“聰明策略”將根據觸發器實例的狀態和配置來決定其行。
1.2.3 SimpleTrigger 常見策略:
- MISFIRE_INSTRUCTION_FIRE_NOW 立刻執行。對於不會重複執行的任務,這是默認的處理策略。
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT 在下一個激活點執行,且超時期內錯過的執行機會作廢。
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_COUNT 立即執行,且超時期內錯過的執行機會作廢。
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT 在下一個激活點執行,並重復到指定的次數。
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_COUNT 立即執行,並重復到指定的次數。
- MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY 忽略所有的超時狀態,按照觸發器的策略執行。
對於 CronTrigger,該“聰明策略”默認選擇 MISFIRE_INSTRUCTION_FIRE_ONCE_NOW以指導其行爲。
1.2.4 CronTrigger 常見策略:
- MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 立刻執行一次,然後就按照正常的計劃執行。
- MISFIRE_INSTRUCTION_DO_NOTHING 目前不執行,然後就按照正常的計劃執行。這意味着如果下次執行時間超過了 end time,實際上就沒有執行機會了。
1.2.5 Job
Job 是 一 個 任 務 接 口 , 開 發 者 定 義 自 己 的 任 務 須 實 現 該 接 口 實 現 void execute(JobExecutionContext context)方法,JobExecutionContext 中提供了調度上下文的各種信息。Job 中的任務有可能併發執行,例如任務的執行時間過長,而每次觸發的時間間隔太短,則會導致任務會被併發執行。如果是併發執行,就需要一個數據庫鎖去避免一個數據被多次處理。可以 execute ()方法上添加註解@DisallowConcurrentExecution 解決這個問題。
1.2.6 JobDetail
Quartz 在每次執行 Job 時,都重新創建一個 Job 實例,所以它不直接接受一個 Job的實例,相反它接收一個 Job 實現類,以便運行時通過 newInstance()的反射機制實例化Job。因此需要通過一個類來描述 Job 的實現類及其它相關的靜態信息,如 Job 名字、描述、關聯監聽器等信息,JobDetail 承擔了這一角色。所以說 JobDetail 是任務的定義,而 Job是任務的執行邏輯。
1.2.7 Calendar
Calendar:org.quartz.Calendar 和 java.util.Calendar 不同,它是一些日曆特定時間點的集合(可以簡單地將 org.quartz.Calendar 看作 java.util.Calendar 的集合——java.util.Calendar 代表一個日曆時間點,無特殊說明後面的 Calendar 即指org.quartz.Calendar)。一個 Trigger 可以和多個 Calendar 關聯,以便排除或包含某些時間點。
1.3 SpringBoot整合Quartz – 實現訂單超時任務
業務需求:
- 說明:當訂單創建之後,如果長時間不支付.則訂單會超時.
- 條件: status=1 未支付. status=6 交易關閉.
- 規定: 30 分鐘超時.
1.3.1 導入jar包
<!--添加 Quartz 的支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
1.3.2 編輯配置類
@Configuration //標識配置類
public class OrderQuartzConfig {
//定義任務詳情
@Bean
public JobDetail orderjobDetail() {
//指定job的名稱和持久化保存任務
return JobBuilder
.newJob(OrderQuartz.class) //引入自己的job
.withIdentity("orderQuartz") //定義任務名稱
.storeDurably()
.build();
}
//定義觸發器
@Bean
public Trigger orderTrigger() {
/*SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMinutes(1) //定義時間週期
.repeatForever();*/
//定義調度器
//"0 0/1 * * * ?" 表示每隔1分鐘執行一次
CronScheduleBuilder scheduleBuilder
= CronScheduleBuilder.cronSchedule("0 0/1 * * * ?");
return TriggerBuilder
.newTrigger()
.forJob(orderjobDetail())
.withIdentity("orderQuartz")
.withSchedule(scheduleBuilder).build();
}
}
1.3.3 定義任務 – OrderQuartz
//準備訂單定時任務
@Component
public class OrderQuartz extends QuartzJobBean{
//修改數據庫的超時訂單的
@Autowired
private OrderMapper orderMapper;
/**
* 條件:30分鐘超時 1改爲6
*判斷依據: 創建訂單的時間 now-created>30分鐘
* created<now-30
*sql: update tb_order set status=6,updated=#{date}
* where created <#{timeOut} and status=1;
*/
@Override
@Transactional
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
//java中專門操作時間的api
Calendar calendar = Calendar.getInstance(); //獲取當前時間
//field操作的時間屬性 分鐘 小時 年 月等
calendar.add(Calendar.MINUTE, -30);
//獲取計算之後的時間
Date timeOut = calendar.getTime();
/**
* entity: 要修改的數據 挑選其中不爲 null 的元素當 set 條件
* updateWrapper: 條件構造器
*/
Order order = new Order();
order.setStatus(6)
.setUpdated(new Date());
UpdateWrapper<Order> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("status", 1)
.lt("created", timeOut);
orderMapper.update(order, updateWrapper);
System.out.println("定時任務完成!!!!!");
}
}