公司最近需要使用Quartz集群来实现任务的动态创建和删除,之前自己只是用过配置好的单机版的,而且是定时
执行的任务,正好借这个机会深入学习一下Quartz。
在正式开始之前,我们先来了解下,spring3.1以下的版本必须使用quartz1.x系列,3.1以上的版本才支持quartz 2.x,不然会出错。至于原因,则是spring对于quartz的支持实现,org.springframework.scheduling.quartz.CronTriggerBean继承了org.quartz.CronTrigger,在quartz1.x系列中org.quartz.CronTrigger是个类,而在quartz2.x系列中org.quartz.CronTrigger变成了接口,从而造成无法用spring的方式配置quartz的触发器(trigger)。公司现运行项目用的spring版本是4.2.2.RELEASE,所有我选取的quartz版本是2.2.1。
最终实现的功能:
1)项目启动时,可动态添加、删除、修改和执行定时任务;
2)因为是集群,一个实例运行挂了,保存在数据库的定时任务可以继续执行;而且定时任务只能被其中一个实例执行。
一、引入Maven坐标
org.quartz-scheduler
quartz
2.2.1
org.quartz-scheduler
quartz-jobs
2.2.1
在Quartz包下docs/dbTables,选择对应的数据库脚本,创建相应的数据库表即可,我用的是mysql5.6,这里有一个需要注意的地方,mysql5.5之前用的表存储引擎是MyISAM,使用的是表级锁,锁发生冲突的概率比较高,并发度低;5.6之后默认的存储引擎为InnoDB,InnoDB采用的锁机制是行级锁,并发度也较高。而quartz集群使用数据库锁的
机制来来实现同一个任务在同一个时刻只被实例执行,所以为了防止冲突,我们建表的时候要选取InnoDB作为表的存
储引擎。如下:
三、配置quartz.xml
CRMscheduler
AUTO
org.quartz.simpl.SimpleThreadPool
20
5
120000
org.quartz.impl.jdbcjobstore.JobStoreTX
true
15000
1
qrtz_
qzDS
四、实现Job接口
我们在动态添加任务的时候需要用到这个类。
public class AllPushNotifyJob implements Job {
private static final Log logger = LogFactory.getLog(AllPushNotifyJob.class);
private AllPushMessageService allPushMessageService;
public AllPushNotifyJob() {
allPushMessageService = (AllPushMessageService) SpringContext.getBeanByName("allPushMessageService");
}
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap();
AllPushMessage allPushMessage = (AllPushMessage) dataMap.get("allPushMessage");
Date expectTriggerTime = allPushMessage.getPush_time();
Date realTriggerTime = new Date();
logger.info("execute category notify job with expect trigger time:" + DateUtils.format(expectTriggerTime, "yyyy-MM-dd HH:mm:ss Z"));
logger.info("real notify time:" + DateUtils.format(realTriggerTime, "yyyy-MM-dd HH:mm:ss Z"));
allPushMessageService.enforceAllPush(allPushMessage);
}
}
@Service
public class AllPushJobScheduler {
private static final Log logger = LogFactory.getLog(AllPushJobScheduler.class);
public void start() {
logger.info("start category update notify scheduler");
schedulerFactoryBean.start();
}
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
/**
* 添加任务
* @param allPushMessage
*/
public void scheduleNotifyJob(AllPushMessage allPushMessage) {
if (allPushMessage.getPush_time().compareTo(new Date()) < 0) {
allPushMessage.setPush_time(DateUtils.addSeconds(new Date(), 10));
}
Scheduler scheduler = schedulerFactoryBean.getScheduler();
JobKey jobKey = getJobKey(allPushMessage);
try {
if (scheduler.checkExists(jobKey)) {
logger.info("all push job existed!:" + jobKey.getName());
return;
}
} catch (SchedulerException e) {
logger.error("get exception:" + e.getMessage(), e);
}
logger.info("schedule all push job at:"+allPushMessage.getPush_time() +" with job name pushID" + jobKey.getName());
JobDataMap jobData = new JobDataMap();
jobData.put("allPushMessage", allPushMessage);
JobDetail notifyJob = JobBuilder.newJob(AllPushNotifyJob.class)
.setJobData(jobData).withIdentity(jobKey).build();
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
trigger.setName("trigger-" + jobKey.getName());
trigger.setJobDetail(notifyJob);
trigger.setStartTime(allPushMessage.getPush_time());
trigger.setRepeatCount(0);
trigger.afterPropertiesSet();
try {
schedulerFactoryBean.getScheduler().scheduleJob(notifyJob,
trigger.getObject());
} catch (SchedulerException e) {
logger.error("get exception when executing quartz job" + e);
}
}
/**
* 删除任务
* @param allPushMessage
* @throws Exception
*/
public void deleteJob(AllPushMessage allPushMessage)throws Exception{
try{
//删除定时任务时 先暂停任务,然后再删除
JobKey jobKey = getJobKey(allPushMessage);
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.pauseJob(jobKey);
scheduler.deleteJob(jobKey);
}catch(Exception e){
System.out.println("删除定时任务失败"+e);
throw new Exception("删除定时任务失败");
}
}
/**
* 获取jobKey
* @param allPushMessage
* @return
*/
public JobKey getJobKey(AllPushMessage allPushMessage) {
return JobKey.jobKey(String.valueOf(allPushMessage.getPush_id()));
}
/**
* 更新定时任务
* @param
* @param
* @throws Exception
*/
public void updateJob(AllPushMessage allPushMessage)throws Exception{
try {
TriggerKey triggerKey =getTriggerKey(String.valueOf(allPushMessage.getPush_id()));
Scheduler scheduler = schedulerFactoryBean.getScheduler();
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
System.out.println("更新定时任务失败"+e);
throw new Exception("更新定时任务失败");
}
}
/**
* 获取触发器key
*
* @param
* @param
* @return
*/
public static TriggerKey getTriggerKey(String jobkey) {
return TriggerKey.triggerKey(jobkey);
}
/**
* 暂停定时任务
* @param allPushMessage
* @throws Exception
*/
public void pauseJob(AllPushMessage allPushMessage) throws Exception {
JobKey jobKey = JobKey.jobKey(String.valueOf(allPushMessage.getPush_id()));
try {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.pauseJob(jobKey);
} catch (SchedulerException e) {
System.out.println("暂停定时任务失败"+e);
throw new Exception("暂停定时任务失败");
}
}
/**
* 恢复任务
* @param
* @param
* @param
* @throws Exception
*/
public void resumeJob(AllPushMessage allPushMessage) throws Exception {
JobKey jobKey = JobKey.jobKey(String.valueOf(allPushMessage.getPush_id()));
try {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.resumeJob(jobKey);
} catch (SchedulerException e) {
System.out.println("恢复定时任务失败"+e);
throw new Exception("恢复定时任务失败");
}
}
/**
* 运行一次任务
* @param allPushMessage
* @throws Exception
*/
public void runOnce(AllPushMessage allPushMessage) throws Exception {
JobKey jobKey = JobKey.jobKey(String.valueOf(allPushMessage.getPush_id()));
try {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.triggerJob(jobKey);
} catch (SchedulerException e) {
System.out.println("运行任务失败"+e);
throw new Exception("运行一次定时任务失败");
}
}
}