调度器核心代码:
import nd.sdp.lcreporting.schedule.model.Schedule;
import nd.sdp.lcreporting.schedule.service.ScheduleService;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class QuartzJobScheduler {
private static final String CRON_GROUP = "CRON_QUARTZ_GROUP";
private static final String TRIGGER_ONCE_QUARTZ_GROUP = "TRIGGER_ONCE_QUARTZ_GROUP";
private static final Logger LOGGER = LoggerFactory.getLogger(QuartzJobScheduler.class);
@Resource
private Scheduler scheduler;
@Resource
private ScheduleService scheduleService;
/**
* 通过schedule生成一个quartz调度任务<br/>
* <p>
* 会先删除name为schedule.getName()对应的任务和触发器,重新调度
* </p>
* @param schedule Schedule
* @throws SchedulerException Scheduler exception
*/
public void scheduleJob(Schedule schedule) throws SchedulerException {
String name = schedule.getName();
deleteJob(name);
TriggerKey triggerKey = TriggerKey.triggerKey(name, CRON_GROUP);
JobKey jobKey = JobKey.jobKey(name, CRON_GROUP);
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(
CronScheduleBuilder.cronSchedule(schedule.getCron())
.withMisfireHandlingInstructionIgnoreMisfires())
.build();
JobDetail jobDetail = JobBuilder.newJob(QuartzJobExecutor.class)
.withIdentity(jobKey)
.build();
jobDetail.getJobDataMap().put(Schedule.SCHEDULE, schedule);
scheduler.scheduleJob(jobDetail, trigger);
}
/**
* 删除 name为name,group为CRON_GROUP对应的任务和触发器
* @param name job name
* @throws SchedulerException scheduler exception
*/
public void deleteJob(String name) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(name, CRON_GROUP);
boolean success = scheduler.deleteJob(jobKey);
if (!success) {
LOGGER.info("job {}-{} not found and deleted.", jobKey.getName(), jobKey.getGroup());
}
}
public void triggerJobNow(Schedule schedule) throws SchedulerException {
schedule.setTriggerOnce(true);
JobDetail jobDetail;
//jobKey name 加上时间窗口后缀,避免拆分后key冲突
Object startTime = schedule.getExtendedProperties().get(ScheduleConstants.TIME_SECTION_START_TIME_KEY);
Object endTime = schedule.getExtendedProperties().get(ScheduleConstants.TIME_SECTION_END_TIME_KEY);
String name = String.format("%s_%s_%s",schedule.getName(), startTime, endTime);
//使用group:TRIGGER_ONCE_QUARTZ_GROUP,与cron任务区分开来
JobKey jobKey = JobKey.jobKey(name, TRIGGER_ONCE_QUARTZ_GROUP);
jobDetail = JobBuilder.newJob(QuartzJobExecutor.class).withIdentity(jobKey)
.storeDurably(true).build();
jobDetail.getJobDataMap().put(Schedule.SCHEDULE, schedule);
try {
this.scheduler.addJob(jobDetail, true);
} catch (SchedulerException e) {
LOGGER.error("triggerJobNow add job fail", e);
throw e;
}
try {
this.scheduler.triggerJob(jobKey);
} catch (SchedulerException e) {
LOGGER.error("triggerJobNow trigger job fail", e);
throw e;
}
}
public int getCurrentlyExecutingJobsCount() throws SchedulerException {
return scheduler.getCurrentlyExecutingJobs().size();
}
}
Controller层:
@RestController
@RequestMapping(value = "/v0.1/schedules")
public class ScheduleController {
private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleController.class);
@Resource
private QuartzJobScheduler quartzJobScheduler;
@Resource
private ScheduleService scheduleService;
@RequestMapping(value = "", method = RequestMethod.POST)
public Object createSchedule(@RequestBody Schedule schedule) {
String message = "调度任务创建成功";
schedule = scheduleService.create(schedule);
try {
quartzJobScheduler.scheduleJob(schedule);
} catch (SchedulerException e) {
message = e.getMessage();
LOGGER.error("schedule job error, will delete schedule", e);
scheduleService.delete(schedule.getName());
}
Map<String, String> result = new HashMap<>();
result.put("message", message);
return result;
}
@RequestMapping(value = "", method = RequestMethod.PUT)
public Object reschedule(@RequestBody Schedule schedule) {
String message = "重新调度任务成功";
schedule = scheduleService.update(schedule);
try {
quartzJobScheduler.scheduleJob(schedule);
} catch (SchedulerException e) {
message = e.getMessage();
LOGGER.error("reschedule job error", e);
}
Map<String, String> result = new HashMap<>();
result.put("message", message);
return result;
}
@RequestMapping(value = "{name}", method = RequestMethod.DELETE)
public Object deleteSchedule(@PathVariable String name) {
String message = "调度任务删除成功";
scheduleService.delete(name);
try {
quartzJobScheduler.deleteJob(name);
} catch (SchedulerException e) {
message = e.getMessage();
LOGGER.error("delete schedule job error", e);
}
Map<String, String> result = new HashMap<>();
result.put("message", message);
return result;
}
@RequestMapping(value = "trigger_once", method = RequestMethod.POST)
public Object triggerOnce(@RequestBody TriggerOnceSchedule triggerOnceSchedule) {
String message = "单次触发调度任务成功";
String scheduleName = triggerOnceSchedule.getScheduleName();
if (StringUtils.isEmpty(scheduleName)) {
throw new BizException(ErrorCode.INVALID_ARGUMENT, "schedule_name不能为空");
}
long startTime = triggerOnceSchedule.getTimeSectionStartTime();
long endTime = triggerOnceSchedule.getTimeSectionEndTime();
if (startTime == 0 || endTime == 0 || startTime > endTime) {
throw new BizException(ErrorCode.INVALID_ARGUMENT, "时间窗参数不正确");
}
List<Schedule.ScheduleJob> jobs;
//从数据库中获取Job配置
if (triggerOnceSchedule.isFetchJobsByScheduleName()) {
Schedule scheduleFromDB = scheduleService.find(triggerOnceSchedule.getScheduleName());
if (scheduleFromDB == null) {
throw new BizException(ErrorCode.DATA_NOT_EXIST, "找不到对应的schedule");
}
jobs = scheduleFromDB.getJobs();
} else {
jobs = triggerOnceSchedule.getJobs();
}
List<Schedule> splitScheduleList = splitSchedule(triggerOnceSchedule, jobs);
for (Schedule schedule : splitScheduleList) {
try {
quartzJobScheduler.triggerJobNow(schedule);
} catch (SchedulerException e) {
message = e.getMessage();
LOGGER.error("trigger job error", e);
break;
}
}
Map<String, Object> result = new HashMap<>();
result.put("message", message);
result.put("schedules", splitScheduleList);
return result;
}
private static final long MAX_TIME_SECTION_SIZE = 2592000000L;//30天
private List<Schedule> splitSchedule(TriggerOnceSchedule triggerOnceSchedule, List<Schedule.ScheduleJob> jobs) {
List<Schedule> scheduleList;
long startTime = triggerOnceSchedule.getTimeSectionStartTime();
long endTime = triggerOnceSchedule.getTimeSectionEndTime();
if (triggerOnceSchedule.getSplitSize() > 1) {//按指定大小拆分
long deltaSize = (long) Math.ceil(1.0*(endTime-startTime)/triggerOnceSchedule.getSplitSize());
scheduleList = splitWithDeltaSize(triggerOnceSchedule, jobs, deltaSize);
} else if (endTime - startTime > MAX_TIME_SECTION_SIZE) { //如果时间窗口大于30天,自动拆分成最大30天的窗口大小
scheduleList = splitWithDeltaSize(triggerOnceSchedule, jobs, MAX_TIME_SECTION_SIZE);
} else { //不拆分
scheduleList = new ArrayList<>(1);
Schedule schedule = buildSchedule(triggerOnceSchedule.getScheduleName(), triggerOnceSchedule.getScheduleDesc(),
jobs, startTime, endTime);
scheduleList.add(schedule);
}
return scheduleList;
}
private List<Schedule> splitWithDeltaSize(TriggerOnceSchedule triggerOnceSchedule, List<Schedule.ScheduleJob> jobs, long deltaSize) {
List<Schedule> scheduleList = new ArrayList<>();
long startTime = triggerOnceSchedule.getTimeSectionStartTime();
long endTime = triggerOnceSchedule.getTimeSectionEndTime();
long splitStartTime = startTime;
long splitEndTime = startTime + deltaSize;
while (splitEndTime <= endTime) {
Schedule schedule = buildSchedule(triggerOnceSchedule.getScheduleName(), triggerOnceSchedule.getScheduleDesc(),
jobs, splitStartTime, splitEndTime);
scheduleList.add(schedule);
splitStartTime = splitEndTime;
splitEndTime += deltaSize;
}
//把最后一段拼上
if (splitStartTime < endTime) {
Schedule schedule = buildSchedule(triggerOnceSchedule.getScheduleName(), triggerOnceSchedule.getScheduleDesc(),
jobs, splitStartTime, endTime);
scheduleList.add(schedule);
}
return scheduleList;
}
private Schedule buildSchedule(String name, String desc, List<Schedule.ScheduleJob> jobs, long startTime, long endTime) {
Schedule schedule = new Schedule();
schedule.setJobs(jobs);
schedule.setName(name);
schedule.setDesc(desc);
schedule.getExtendedProperties().put(ScheduleConstants.TIME_SECTION_START_TIME_KEY, startTime);
schedule.getExtendedProperties().put(ScheduleConstants.TIME_SECTION_END_TIME_KEY, endTime);
return schedule;
}
}