#Quartz#
现象描述 :
项目启动要初始化程序配置的各个job, 采用的是quartz,但是发现有时候启动时报错:org.quartz.ObjectAlreadyExistsException: Unable to store Job : 'MONITOR_RUNNER_GROUP.indexWarningRunner', because one already exists with thi
s identification, job存储失败。
查看quartz数据库: cron_triggers,里面没有 MONITOR_RUNNER_GROUP.indexWarningRunner, job_details库里面有,triggers库里面没有。
开始做法:
存不进去是因为里面已经有了,删除job_details里面的,启动正常,但是后续还会出现这种情况。代码逻辑有问题,不是什么quartz bug。
排查:
初始化quartz任务代码:
public void init() throws Exception {
Map<String, BaseRunner> beansOfType = SpringContextUtil.getApplicationContext().getBeansOfType(BaseRunner.class);
for (String name : beansOfType.keySet()) {
BaseRunner baseRunner = beansOfType.get(name);
JobKey jobKey = new JobKey(name, RunnerConstants.RUNNER_GROUP);
TriggerKey triggerKey = new TriggerKey(name, RunnerConstants.RUNNER_GROUP);RunnerCron annotation = baseRunner.getClass().getAnnotation(RunnerCron.class);
if (annotation == null) {
LOG.warn("monitor schedule: {} create failed, cron is missing", jobKey.toString());
this.tryDeleteJob(jobKey);
continue;
}
String cron = this.getProperties(annotation.value());
if (StringUtils.isNotBlank(cron)) {
JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
jobDetailFactoryBean.setGroup(RunnerConstants.RUNNER_GROUP);
jobDetailFactoryBean.setName(name);
jobDetailFactoryBean.setDurability(true);
jobDetailFactoryBean.setJobClass(RunnerJob.class);
jobDetailFactoryBean.getJobDataMap().put(RunnerConstants.RUNNER_NAME, name);
jobDetailFactoryBean.afterPropertiesSet();
JobDetail jobDetail = jobDetailFactoryBean.getObject();CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (Objects.isNull(trigger) ) {
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(CronScheduleBuilder.cronSchedule(cron)).startNow().build();
scheduler.scheduleJob(jobDetail, cronTrigger);
LOG.info("monitor schedule: {} created", triggerKey.toString());
} else {
CronTrigger cronTrigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(CronScheduleBuilder.cronSchedule(cron)).startNow().build();
scheduler.rescheduleJob(triggerKey, cronTrigger);
LOG.info("monitor schedule: {} refreshed", triggerKey.toString());
}
} else {
LOG.warn("monitor schedule: {} create failed, cron is invalid", jobKey.toString());
this.tryDeleteJob(jobKey);
}
}
}private void tryDeleteJob(JobKey jobKey) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup());
scheduler.pauseTrigger(triggerKey);
//这句话是先删除相关的trigger触发器,如果trigger关联的job没有其他触发器并且不持续,也被删除(duarble为true,只删除trigger,job还在保留)
//否则下次启动就有可能存储job的时候报错
//scheduler.unscheduleJob(triggerKey);//会删除joddetail,以及其关联的triggers
scheduler.deleteJob(jobKey);
}
可以看出来,如果trigger 是空,但是job 数据库还是有的。然后scheduleJob方法,存储job报错了。
最终做法是:
虽然,一个job可以绑定多个trigger,一个trigger只能有一个job, 但是这里就做成了一对一的关系,避免混乱:判断trigger和jobDetail 有没有,都有,就更新trigger,否则deleteJob ,然后新建。或者,启动的时候一并删除,然后全部新建