Quartz分布式定時(shí)任務(wù)的暫停和恢復(fù)等:
前兩篇我們了解了quartz分布式定時(shí)任務(wù)的基本原理和實(shí)現(xiàn)方式,知道所有的定時(shí)任務(wù)都會(huì)被持久化到數(shù)據(jù)庫(kù)求妹。那么我們肯定可以通過(guò)操作數(shù)據(jù)庫(kù)來(lái)做定時(shí)任務(wù)的暫停,恢復(fù)佳窑,立即啟動(dòng)制恍,添加等操作。
事實(shí)上神凑,quartz已經(jīng)給我們提供來(lái)一些列的api接口來(lái)操作對(duì)應(yīng)的定時(shí)任務(wù)净神,我們只需要在這個(gè)基礎(chǔ)之上做進(jìn)一步的擴(kuò)展和封裝就可以實(shí)現(xiàn)我們自己業(yè)務(wù),下面耙厚,將圍繞定時(shí)任務(wù)的控制强挫,提供一個(gè)簡(jiǎn)單的實(shí)現(xiàn)方式。
使用的環(huán)境版本:spring4.x+quartz2.2.x
1,首先薛躬,我們需要?jiǎng)?chuàng)建一個(gè)我們自己job的實(shí)體類ScheduleJob:
/**
* Created by lyndon on 16/9/13. * job的實(shí)體類
*/
public class ScheduleJob {
private String jobNo; //任務(wù)編號(hào)
private String jobName; //任務(wù)名稱
private String jobGroup; //任務(wù)所屬組
private String desc; //任務(wù)描述
private String jobStatus; //任務(wù)狀態(tài)
private String cronExpression; //任務(wù)對(duì)應(yīng)的時(shí)間表達(dá)式
private String triggerName; //觸發(fā)器名稱
//此處省略get和set方法
}
2, 創(chuàng)建我們自己的QuartzImplService服務(wù)層:
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
/**
* Created by lyndon on 16/9/13.
* quartz_job的工具類
*/
@Service
public class QuartzUtils {
private final Logger logger = LoggerFactory.getLogger(QuartzUtils.class);
@Resource
private Scheduler scheduler;
/**
*
* 獲取計(jì)劃任務(wù)列表
* @return List<ScheduleJob>
*/
public List<ScheduleJob> getPlanJobList() throws SchedulerException{
List<ScheduleJob> jobList = new ArrayList<>();
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);;
jobKeys = scheduler.getJobKeys(matcher);
for (JobKey jobKey : jobKeys) {
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
for (Trigger trigger : triggers) {
ScheduleJob job = new ScheduleJob();
job.setJobName(jobKey.getName());
job.setJobGroup(jobKey.getGroup());
// 此處是我自己業(yè)務(wù)需要俯渤,給每個(gè)定時(shí)任務(wù)配置類對(duì)應(yīng)的編號(hào)和描述
String value = PropertiesUtils.getStringCN(jobKey.getName());
if(null != value && !"".equals(value)){
job.setJobNo(value.split("/")[0]);
job.setDesc(value.split("/")[1]);
}else{
job.setJobNo("0000");
job.setDesc("未監(jiān)控任務(wù)");
}
job.setTriggerName("觸發(fā)器:" + trigger.getKey());
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
job.setJobStatus(triggerState.name());
if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
String cronExpression = cronTrigger.getCronExpression();
job.setCronExpression(cronExpression);
}
jobList.add(job);
}
}
// 對(duì)返回的定時(shí)任務(wù)安裝編號(hào)做排序
Collections.sort(jobList,new Comparator<ScheduleJob>(){
public int compare(ScheduleJob arg0, ScheduleJob arg1) {
return arg0.getJobNo().compareTo(arg1.getJobNo());
}
});
return jobList;
}
/**
* 獲取正在運(yùn)行的任務(wù)列表
* @return List<ScheduleJob>
*/
public List<ScheduleJob> getCurrentJobList() throws SchedulerException{
List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();;
List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size());;
for (JobExecutionContext executingJob : executingJobs) {
ScheduleJob job = new ScheduleJob();
JobDetail jobDetail = executingJob.getJobDetail();
JobKey jobKey = jobDetail.getKey();
Trigger trigger = executingJob.getTrigger();
job.setJobName(jobKey.getName());
job.setJobGroup(jobKey.getGroup());
String value = PropertiesUtils.getStringCN(jobKey.getName());
if(null != value && !"".equals(value)){
job.setJobNo(value.split("/")[0]);
job.setDesc(value.split("/")[1]);
}else{
job.setJobNo("0000");
job.setDesc("未監(jiān)控任務(wù)");
}
job.setTriggerName("觸發(fā)器:" + trigger.getKey());
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
job.setJobStatus(triggerState.name());
if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
String cronExpression = cronTrigger.getCronExpression();
job.setCronExpression(cronExpression);
}
jobList.add(job);
}
Collections.sort(jobList,new Comparator<ScheduleJob>(){
public int compare(ScheduleJob arg0, ScheduleJob arg1) {
return arg0.getJobNo().compareTo(arg1.getJobNo());
}
});
return jobList;
}
/**
* 暫停當(dāng)前任務(wù)
* @param scheduleJob
*/
public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException{
JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
if(scheduler.checkExists(jobKey)){
scheduler.pauseJob(jobKey);
}
}
/**
* 恢復(fù)當(dāng)前任務(wù)
* @param scheduleJob
*/
public void resumeJob(ScheduleJob scheduleJob) throws SchedulerException{
JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
if(scheduler.checkExists(jobKey)){
//并恢復(fù)
scheduler.resumeJob(jobKey);
//重置當(dāng)前時(shí)間
this.rescheduleJob(scheduleJob);
}
}
/**
* 刪除任務(wù)
* @param scheduleJob
* @return boolean
*/
public boolean deleteJob(ScheduleJob scheduleJob) throws SchedulerException{
JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
if(scheduler.checkExists(jobKey)){
return scheduler.deleteJob(jobKey);
}
return false;
}
/**
* 立即觸發(fā)當(dāng)前任務(wù)
* @param scheduleJob
*/
public void triggerJob(ScheduleJob scheduleJob) throws SchedulerException{
JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
if(scheduler.checkExists(jobKey)){
scheduler.triggerJob(jobKey);
}
}
/**
* 更新任務(wù)的時(shí)間表達(dá)式
* @param scheduleJob
* @return Date
*/
public Date rescheduleJob(ScheduleJob scheduleJob) throws SchedulerException{
TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(),
scheduleJob.getJobGroup());
if(scheduler.checkExists(triggerKey)){
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob
.getCronExpression());
//按新的cronExpression表達(dá)式重新構(gòu)建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
.withSchedule(scheduleBuilder).build();
//按新的trigger重新設(shè)置job執(zhí)行
return scheduler.rescheduleJob(triggerKey, trigger);
}
return null;
}
/**
* 查詢其中一個(gè)任務(wù)的狀態(tài)
* @param scheduleJob
* @return
* @throws SchedulerException
*/
public String scheduleJob(ScheduleJob scheduleJob) throws SchedulerException {
String status = null;
TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (null != trigger) {
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
status = triggerState.name();
}
return status;
}
/**
* 校驗(yàn)job是否已經(jīng)加載
* @param scheduleJob JOB基本信息參數(shù)
* @return 是否已經(jīng)加載
*/
public boolean checkJobExisted(ScheduleJob scheduleJob) throws SchedulerException {
return scheduler.checkExists(new JobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()));
}
private String getStatuDesc(String status){
if(status.equalsIgnoreCase("NORMAL")){
return "正常";
}else if(status.equalsIgnoreCase("PAUSED")){
return "暫停";
}else{
return "異常";
}
}
}
3,提供對(duì)應(yīng)的Controller
import com.innmall.hotelmanager.common.Result;
import com.innmall.hotelmanager.service.quartz.QuartzUtils;
import com.innmall.hotelmanager.service.quartz.ScheduleJob;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* Created by lyndon on 16/9/13.
*/
@RestController
@RequestMapping(value = {"/v1/job"})
public class QuartzController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private QuartzUtils quartzUtils;
//獲取定時(shí)任務(wù)的列表
@RequestMapping(value = {"/getJobList"})
public Result getPlanJobList(String openId){
//QuartzUtils quartzUtils = new QuartzUtils();
List<ScheduleJob> list = null;
try {
list = quartzUtils.getPlanJobList();
} catch (SchedulerException e) {
e.printStackTrace();
}
return Result.success(list);
}
//暫停任務(wù)
@RequestMapping(value = {"/pauseJob"})
public Result pauseJob(String openId){
//QuartzUtils quartzUtils = new QuartzUtils();
ScheduleJob job = new ScheduleJob();
job.setJobGroup("innmall_job");
job.setJobName("refreshWxToKenJobDetail");
try {
quartzUtils.pauseJob(job);
} catch (SchedulerException e) {
e.printStackTrace();
}
return Result.success("暫停成功");
}
//恢復(fù)任務(wù)
@RequestMapping(value = {"/resumeJob"})
public Result resumeJob(String openId){
//QuartzUtils quartzUtils = new QuartzUtils();
ScheduleJob job = new ScheduleJob();
job.setJobGroup("innmall_job");
job.setJobName("refreshWxToKenJobDetail");
try {
quartzUtils.resumeJob(job);
} catch (SchedulerException e) {
e.printStackTrace();
}
return Result.success("恢復(fù)成功");
}
//立即觸發(fā)任務(wù)
@RequestMapping(value = {"/triggerJob"})
public Result triggerJob(String openId){
//QuartzUtils quartzUtils = new QuartzUtils();
ScheduleJob job = new ScheduleJob();
job.setJobGroup("innmall_job");
job.setJobName("refreshWxToKenJobDetail");
try {
quartzUtils.triggerJob(job);
} catch (SchedulerException e) {
e.printStackTrace();
}
return Result.success("觸發(fā)成功");
}
//刪除任務(wù)
@RequestMapping(value = {"/deleteJob"})
public Result deleteJob(String openId){
//QuartzUtils quartzUtils = new QuartzUtils();
ScheduleJob job = new ScheduleJob();
job.setJobGroup("innmall_job");
job.setJobName("refreshWxToKenJobDetail");
try {
quartzUtils.deleteJob(job);
} catch (SchedulerException e) {
e.printStackTrace();
}
return Result.success("觸發(fā)成功");
}
}
4型宝,接下來(lái)八匠,我們就可以進(jìn)行單元測(cè)試了絮爷。
5,需要注意的地方:
5.1 service層:
@Resource
private Scheduler scheduler;
這里是因?yàn)槲覀冊(cè)趚ml里面已經(jīng)配置對(duì)應(yīng)的工廠bean梨树,所以可以在這里可以直接注入:
<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
destroy-method="destroy">
5.2 關(guān)于區(qū)分不同業(yè)務(wù)的觸發(fā)器和任務(wù)坑夯,可以配置job和trigger的group屬性,這樣我們便以區(qū)分抡四,如果不設(shè)置柜蜈,quartz將使用default關(guān)鍵字:
<bean id="refreshWxToKenJobDetail"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.innmall.hotelmanager.timer.RefreshWxToKen"/>
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
<property name="group" value="innmall_job"/>
</bean>
<bean id="refreshWxToKenTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="refreshWxToKenJobDetail"/>
<!-- 每10s鐘運(yùn)行一次 -->
<property name="cronExpression" value="0/10 * * * * ?"/>
<property name="misfireInstruction" value="2"/>
<property name="group" value="innmall_trigger"/>
</bean>
5.3 關(guān)于定時(shí)任務(wù)恢復(fù)后,我們?nèi)绻恍枰屩板e(cuò)過(guò)的定時(shí)任務(wù)再執(zhí)行一次指巡,可以設(shè)置misfireInstruction的屬性淑履,其實(shí)就是
CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING
進(jìn)去可以看見(jiàn)對(duì)應(yīng)的值為2.
并且需要在我們恢復(fù)任務(wù)的時(shí)候調(diào)用更新的方法,可以見(jiàn)上文的QuartzUtil中的方法藻雪。
//重置當(dāng)前時(shí)間
this.rescheduleJob(scheduleJob);
5.4 如果需要定時(shí)任務(wù)恢復(fù)后秘噪,需要將之前錯(cuò)過(guò)的執(zhí)行一次,那么只需要在xml里面去除misfireInstruction屬性勉耀,其實(shí)就是使用默認(rèn)配置指煎,并且在恢復(fù)的時(shí)候不調(diào)用更新的方法。
關(guān)于quartz的使用方法便斥,暫時(shí)就介紹到這里至壤,如果有什么地方有問(wèn)題,歡迎指正椭住,后面將持續(xù)研究對(duì)應(yīng)的異常處理機(jī)制崇渗,敬請(qǐng)關(guān)注~