一、需求
由于可能要动态调整某些定时任务的执行,然而目前WEB方面的现状是所有定时任务是利用spring @Scheduled注解方式实现,不方便动态在调整定时任务的执行时间
二、分析
在Spring中使用Quartz有两种方式实现:第一种是任务类继承QuartzJobBean,第二种则是在配置文件里定义任务类和要执行的方法,类和方法可以是普通类。因此第二种方式远比第一种方式来的灵活。所以这个分析设计也是基于spring配置的方式
Quartz中是以分组名+任务名作为任务的唯一key,为与quartz中的实现方式一致,本设计也采用这样的方式
注:spring @Scheduled注解方式,定时任务的执行Job在内存,不方便动态调整,并且不支持比如最后一个工作0 0 17 ? * MON-FRI 0 0 15 LW * ?等诸如此类的cron表达式,功能相对quartz比较弱些
三、实现
3.1 数据表的设计
直接上表吧
CREATE
TABLE
`schedule_job` (
`job_id`
varchar
(45)
NOT
NULL
DEFAULT
''
COMMENT
'任务id,用于区分业务'
,
`job_name`
varchar
(64)
NOT
NULL
DEFAULT
''
COMMENT
'任务名称'
,
`job_group`
varchar
(64)
NOT
NULL
DEFAULT
'DEFAULT'
COMMENT
'任务分组'
,
`job_status`
varchar
(32)
DEFAULT
'1'
COMMENT
'任务状态 0禁用 1启用 2删除'
,
`cron_expression`
varchar
(64)
DEFAULT
NULL
COMMENT
'任务运行时间表达式'
,
`job_desc`
varchar
(256)
DEFAULT
NULL
COMMENT
'任务描述'
,
`create_time`
int
(11)
DEFAULT
NULL
,
`update_time`
int
(11)
DEFAULT
NULL
,
`operator`
varchar
(64)
DEFAULT
NULL
,
PRIMARY
KEY
(`job_group`,`job_name`)
) ENGINE=InnoDB
DEFAULT
CHARSET=utf8 COMMENT=
'动态任务调度控制'
;
|
3.2 Quartz 的trigger各种状态说明
- None:Trigger已经完成,且不会在执行,或者找不到该触发器,或者Trigger已经被删除
- NORMAL:正常状态
- PAUSED:暂停状态
- COMPLETE:触发器完成,但是任务可能还正在执行中
- BLOCKED:线程阻塞状态
- ERROR:出现错误
3.3 动态暂停 恢复 修改和删除任务
Quartz的任务信息默认是保存的内存里的,没有保存到数据库,使用的是RAMJobStore,当然如果你有需要,可以实现成JDBCJobStore,那样任务信息将会更全面
1、获取计划中的任务
计划中的任务通过配置文件配置,详细配置如下
<!---->
<
bean
id
=
"
class
=
"cn.XXXX.Task"
/>
<!---->
<
bean
id
=
"xxxxJobDetail"
class
=
"org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
>
<
property
name
=
"group"
value
=
"xxx"
/>
<
property
name
=
"name"
value
=
"xxxx"
/>
<
property
name
=
"concurrent"
value
=
"false"
/>
<
property
name
=
"targetObject"
>
<
ref
bean
=
"
/>
</
property
>
<
property
name
=
"targetMethod"
>
<
value
>xxxxx</
value
>
</
property
>
</
bean
>
<
bean
id
=
"xxxxxTrigger"
class
=
"org.springframework.scheduling.quartz.CronTriggerFactoryBean"
>
<
property
name
=
"name"
value
=
"xxxx"
/>
<
property
name
=
"group"
value
=
"xxxx"
/>
<
property
name
=
"jobDetail"
>
<
ref
bean
=
"
/>
</
property
>
<
property
name
=
"cronExpression"
>
<!-- 0 0 17 ? * MON-FRI-->
<
value
>${task.cron.xxxx}</
value
>
</
property
>
</
bean
>
<!-- 调度工厂 -->
<
bean
id
=
"scheduler"
class
=
"org.springframework.scheduling.quartz.SchedulerFactoryBean"
>
<
property
name
=
"triggers"
>
<
list
>
<!---->
<
ref
bean
=
"
/>
</
list
>
</
property
>
</
bean
>
|
/**
* 计划中的任务
*
* @param
* @return
* @author hongshu
* Created By 2017/9/21 13:54
*/
public
List<ScheduleJob> plannedJob() {
try
{
Scheduler scheduler = schedulerFactoryBean.getScheduler();
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
List<ScheduleJob> jobList =
new
ArrayList();
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());
job.setJobDesc(
"触发器:"
+ 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);
}
}
return
jobList;
}
catch
(Exception e) {
logger.error(
"plannedJob "
+ e.getMessage(), e);
}
return
null
;
}
|
2、暂停与恢复任务
/**
* 恢复任务
*
* @param
* @return
* @author hongshu
* Created By 2017/9/21 13:54
*/
public
void
resumeJob(String jobName, String jobGroup) {
try
{
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.resumeJob(jobKey);
logger.error(
"恢复任务"
+ jobGroup +
"."
+ jobName +
"成功"
);
}
catch
(SchedulerException e) {
logger.error(
"恢复任务"
+ jobGroup +
"."
+ jobName +
"异常:"
+ e.getMessage(), e);
}
}
/**
* 暂停任务
*
* @param
* @return
* @author hongshu
* Created By 2017/9/21 13:54
*/
public
void
pauseJob(String jobName, String jobGroup) {
try
{
Scheduler scheduler = schedulerFactoryBean.getScheduler();
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
scheduler.pauseJob(jobKey);
logger.error(
"暂停任务"
+ jobGroup +
"."
+ jobName +
"成功"
);
}
catch
(SchedulerException e) {
logger.error(
"暂停任务"
+ jobGroup +
"."
+ jobName +
"异常:"
+ e.getMessage(), e);
}
}
|
3、更新cronExpression
/**
* 更新定时任务cornExpression
*
* @param
* @return
* @author hongshu
* Created By 2017/9/20 15:52
*/
public
void
loadCronExpression() {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
// List<ScheduleJob> runningJob = runningJob();
/*查询计划中的任务*/
List<ScheduleJob> plannedJob = plannedJob();
for
(ScheduleJob job : plannedJob) {
try
{
TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
//获取trigger,即在spring配置文件中定义的 bean id="myTrigger"
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
//不存在,创建一个
if
(
null
== trigger) {
JobDetail jobDetail = JobBuilder.newJob(QuartzJobFactory.
class
)
.withIdentity(job.getJobName(), job.getJobGroup()).build();
jobDetail.getJobDataMap().put(
"scheduleJob"
, job);
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job
.getCronExpression());
//按新的cronExpression表达式构建一个新的trigger
trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup()).withSchedule(scheduleBuilder).startNow().build();
scheduler.scheduleJob(jobDetail, trigger);
}
else
{
ScheduleJob dbJob = scheduleJobService.queryScheduleJobByPriKey(job.getJobName(), job.getJobGroup());
if
(dbJob !=
null
&& StringUtils.isNotBlank(dbJob.getCronExpression())) {
/*暂停任务*/
pauseJob(job.getJobName(), job.getJobGroup());
Date now =
new
Date();
Date rawFireTimeAfter = trigger.getFireTimeAfter(now);
String rawCron = trigger.getCronExpression();
// Trigger已存在,那么更新相应的定时设置
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(dbJob.getCronExpression());
//按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
.withSchedule(scheduleBuilder).startNow().build();
Date fireTimeAfter = trigger.getFireTimeAfter(now);
String newCron = trigger.getCronExpression();
//按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
/*恢复任务*/
resumeJob(job.getJobName(), job.getJobGroup());
logger.info(
"定时任务更新,cron调整前{},调整后{}"
, rawCron, newCron);
logger.info(
"定时任务更新,下次开火时间调整前{},调整后{}"
, DateFormatUtils.format(rawFireTimeAfter,
"yyyy-MM-dd HH:mm:ss"
), DateFormatUtils.format(fireTimeAfter,
"yyyy-MM-dd HH:mm:ss"
));
}
}
}
catch
(Exception e) {
logger.error(
"动态任务调整"
+ e.getMessage(), e);
}
}
}
|
4、动态添加新的任务
由于定时任务会处理相应的业务逻辑,动态添加新的定时任务不做过多介绍,在更新cronExpression的实现中,当没有发现trriger时,会自动创建新的定时任务,然后有一个
QuartzJobFactory类会执行新添加的任务,只时做 了打印处理
/**
* 动态定时任务运行工厂类
*
* @author hongshu
* Created By 2017/9/20 10:39.
*/
@Component
public
class
QuartzJobFactory
implements
Job{
private
static
final
Logger logger = Logger.getLogger(QuartzJobFactory.
class
);
@Override
public
void
execute(JobExecutionContext context)
throws
JobExecutionException {
logger.info(
"任务成功运行"
);
ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get(
"scheduleJob"
);
logger.info(
"任务名称 = ["
+ scheduleJob.getJobName() +
"]"
);
}
}
|
注意:JobDetail与Trigger中的name、group属性要保持一致,group默认为default
此处的dbJob是存储于Mysql,根据jobName+jobGroup动态更新定时任务的执行时间
四、使用
当系统接收到更新Quzrtz的指令后,系统去更新数据库,数据更新成功后,调用loadCronExpression()去更新Quartz的定时任务执行计划。