最近需要做一个简单的监控系统,需要定时任务查询不同数据库表的数据是否正常,需要动态的增加定时任务与监控SQL,这里简单记一下
这里用的springboot 是1.5.10,如果是2.x版本已经集成了quartz
org.springframework.boot
spring-boot-starter-parent
1.5.10.RELEASE
org.quartz-scheduler
quartz
2.3.0
@Data
public class SchedulerJobEntity extends BaseEntity {
private static final long serialVersionUID = 6678656140297128519L;
private String jobName;
private String beanName;
private String methodName;
private String params;
private String cronExpression;
private String status;
private String remark;
}
任务执行时,根据SchedulerJobEntity中的类名和方法名称获取需要执行的定时任务,JobContext用于参数传递
@Data
public class JobContext implements Serializable {
private static final long serialVersionUID = -8127498514244724662L;
/**
* 任务ID
*/
private Integer jobId;
/**
* 任务名称
*/
private String jobName;
/**
* jobEntity的参数
*/
private String params;
/**
* 执行cron表达式
*/
private String cronExpression;
/**
* 如果{@link SchedulerJobEntity#getParams()}是json格式,那么会把里面的值存入此map,这里也可以加入其它参数
*/
private Map paramMap = new HashMap<>();
/**
* 执行结果
*/
private Map resultMap = new HashMap<>();
}
@Slf4j
public class ScheduleJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
SchedulerJobEntity jobEntity = (SchedulerJobEntity) context.getMergedJobDataMap().get(JOB_PARAM_KEY);
SchedulerJobLogService scheduleJobLogService = SpringUtil.getBean(SchedulerJobLogService.class);
SchedulerJobLogEntity logEntity = new SchedulerJobLogEntity();
logEntity.setJobId(jobEntity.getId());
logEntity.setJobName(jobEntity.getJobName());
logEntity.setParams(jobEntity.getParams());
logEntity.setCreated(new Date());
long startTime = System.currentTimeMillis();
try {
MDC.put(Constant.JC_REQ_ID, LogUtil.generateReqId());
log.info("任务准备执行,任务ID:{}, 名称={}", jobEntity.getId(), jobEntity.getJobName());
doTask(jobEntity);
long times = System.currentTimeMillis() - startTime;
logEntity.setTimePeriod((int) times);
logEntity.setStatus(TaskStatus.success.name());
log.info("任务执行完毕,任务ID:{}, 名称={} 总共耗时:{}毫秒", jobEntity.getId(), jobEntity.getJobName(), times);
if (times > 5000) {
// 超长时间的任务记录日志
scheduleJobLogService.save(logEntity);
}
} catch (Exception e) {
log.error("任务执行失败,任务ID:{}, 名称={}", jobEntity.getId(), jobEntity.getJobName(), e);
long times = System.currentTimeMillis() - startTime;
logEntity.setTimePeriod((int) times);
logEntity.setStatus(TaskStatus.failure.name());
logEntity.setErrorMsg(StringUtils.left(ExceptionUtils.getStackTrace(e), 255));
scheduleJobLogService.save(logEntity);
}
}
private void doTask(SchedulerJobEntity jobEntity) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String beanName = jobEntity.getBeanName();
Object target;
if (StringUtils.isEmpty(beanName)) {
target = SpringUtil.getBean(MonitorTask.class);
} else {
target = SpringUtil.getBean(beanName);
}
String methodName = StringUtils.defaultIfBlank(jobEntity.getMethodName(), "monitorJob");
Method method = target.getClass().getDeclaredMethod(methodName, JobContext.class);
JobContext jobContext = JoddBeanUtil.copyBean(jobEntity, JobContext.class);
if(StringUtils.isNotEmpty(jobEntity.getParams())) {
try {
jobContext.getParamMap().putAll(JSON.parseObject(jobEntity.getParams()));
} catch (Exception e) {
log.error("参数解析失败,params不是JSON 格式,params={}", jobEntity.getParams(), e);
}
}
jobContext.setJobId(jobEntity.getId());
method.invoke(target, jobContext);
}
}
public class SchedulerUtils {
private final static String JOB_NAME = "TASK_";
public final static String JOB_PARAM_KEY = "TASK_PARAM_";
private static TriggerKey getTriggerKey(Integer jobId) {
return TriggerKey.triggerKey(JOB_NAME + jobId);
}
private static JobKey getJobKey(Integer jobId) {
return JobKey.jobKey(JOB_NAME + jobId);
}
public static CronTrigger getCronTrigger(Scheduler scheduler, Integer jobId) {
try {
return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));
} catch (SchedulerException e) {
throw new RuntimeException("获取定时任务CronTrigger出现异常", e);
}
}
public static void createSchedulerJob(Scheduler scheduler, SchedulerJobEntity jobEntity) {
try {
JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(jobEntity.getId())).build();
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobEntity.getCronExpression())
.withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobEntity.getId())).withSchedule(scheduleBuilder)
.build();
jobDetail.getJobDataMap().put(JOB_PARAM_KEY, jobEntity);
scheduler.scheduleJob(jobDetail, trigger);
if (!StatusEnum.online.name().equalsIgnoreCase(jobEntity.getStatus())) {
pauseJob(scheduler, jobEntity.getId());
}
} catch (SchedulerException e) {
throw new RuntimeException("创建定时任务失败", e);
}
}
public static void updateSchedulerJob(Scheduler scheduler, SchedulerJobEntity jobEntity) {
try {
TriggerKey triggerKey = getTriggerKey(jobEntity.getId());
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobEntity.getCronExpression())
.withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = getCronTrigger(scheduler, jobEntity.getId());
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
trigger.getJobDataMap().put(JOB_PARAM_KEY, jobEntity);
scheduler.rescheduleJob(triggerKey, trigger);
if (!StatusEnum.online.name().equalsIgnoreCase(jobEntity.getStatus())) {
pauseJob(scheduler, jobEntity.getId());
}
} catch (SchedulerException e) {
throw new RuntimeException("更新定时任务失败", e);
}
}
public static void run(Scheduler scheduler, Integer jobId) {
try {
scheduler.triggerJob(getJobKey(jobId));
} catch (SchedulerException e) {
throw new RuntimeException("立即执行定时任务失败", e);
}
}
public static void pauseJob(Scheduler scheduler, Integer jobId) {
try {
scheduler.pauseJob(getJobKey(jobId));
} catch (SchedulerException e) {
throw new RuntimeException("暂停定时任务失败", e);
}
}
public static void resumeJob(Scheduler scheduler, Integer jobId) {
try {
scheduler.resumeJob(getJobKey(jobId));
} catch (SchedulerException e) {
throw new RuntimeException("恢复定时任务失败", e);
}
}
public static void deleteScheduleJob(Scheduler scheduler, Integer jobId) {
try {
scheduler.deleteJob(getJobKey(jobId));
} catch (SchedulerException e) {
throw new RuntimeException("删除定时任务失败", e);
}
}
}
项目启动时初始化任务,任务增删改查的时候同步修改任务计划
@Slf4j
@Service
public class SchedulerJobBizImpl implements SchedulerJobBiz {
@Resource
private SchedulerJobService schedulerJobService;
@Resource
private Scheduler scheduler;
/**
* 初始化任务
*/
@PostConstruct
public void init() {
List scheduleJobList = schedulerJobService.list();
for (SchedulerJobEntity jobEntity : scheduleJobList) {
try {
CronTrigger cronTrigger = SchedulerUtils.getCronTrigger(scheduler, jobEntity.getId());
if (cronTrigger == null) {
SchedulerUtils.createSchedulerJob(scheduler, jobEntity);
} else {
SchedulerUtils.updateSchedulerJob(scheduler, jobEntity);
}
} catch (Exception e) {
log.error("启动任务失败", e);
}
}
}
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void save(SchedulerJobEntity jobEntity) {
jobEntity.setStatus(StatusEnum.online.name());
schedulerJobService.save(jobEntity);
SchedulerUtils.createSchedulerJob(scheduler, jobEntity);
}
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void update(SchedulerJobEntity jobEntity) {
schedulerJobService.updateById(jobEntity);
SchedulerUtils.updateSchedulerJob(scheduler, jobEntity);
}
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void delete(SchedulerJobEntity jobEntity) {
SchedulerUtils.deleteScheduleJob(scheduler, jobEntity.getId());
jobEntity.setStatus(StatusEnum.offline.name());
jobEntity.setDeleted(new Date());
schedulerJobService.updateById(jobEntity);
}
@Override
public void run(Integer[] jobIds) {
for (Integer jobId : jobIds) {
SchedulerUtils.run(scheduler, jobId);
}
}
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void pause(Integer[] jobIds) {
for (Integer jobId : jobIds) {
SchedulerUtils.pauseJob(scheduler, jobId);
}
updateBatch(jobIds, StatusEnum.offline);
}
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void resume(Integer[] jobIds) {
for (Integer jobId : jobIds) {
SchedulerUtils.resumeJob(scheduler, jobId);
}
updateBatch(jobIds, StatusEnum.online);
}
private void updateBatch(Integer[] jobIds, StatusEnum status) {
schedulerJobService.batchUpdateStatus(jobIds, status.name());
}
}
定时任务的基本配置
@Configuration
public class SchedulerConfig {
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
Properties prop = new Properties();
prop.put("org.quartz.scheduler.instanceName", "JcMonitorScheduler");
prop.put("org.quartz.scheduler.instanceId", "AUTO");
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "15");
prop.put("org.quartz.threadPool.threadPriority", "5");
factory.setQuartzProperties(prop);
factory.setSchedulerName("JcMonitorScheduler");
factory.setStartupDelay(30);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
factory.setOverwriteExistingJobs(true);
//这样当spring关闭时,会等待所有已经启动的quartz job结束后spring才能完全shutdown。
factory.setWaitForJobsToCompleteOnShutdown(true);
factory.setAutoStartup(true);
return factory;
}
}
这里controller类和dao层代码没有贴出来。