之前项目中使用springMVC集成了可配置的定时任务,现spring cloud也需要集成,单纯的springMVC和springboot的集成基本没有什么区别,但是在spring cloud中分出需要服务和集群,就需要对定时任务进行一定的改造。源码部分由max chen提供!
一、数据库字段
1、task_schedule_job表,用来存储定时任务的基本信息
CREATE TABLE `task_schedule_job` (
`job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`job_name` varchar(255) DEFAULT NULL COMMENT '定时任务名称',
`job_group` varchar(255) DEFAULT NULL COMMENT '所属服务',
`job_status` varchar(255) DEFAULT NULL COMMENT '是否开启',
`cron_expression` varchar(255) NOT NULL COMMENT 'cron表达式',
`description` varchar(255) DEFAULT '0' COMMENT '描述 0:集群全部执行 1:单节点执行',
`bean_class` varchar(255) DEFAULT NULL COMMENT '类路径',
`is_concurrent` varchar(255) DEFAULT NULL COMMENT '是否需要顺序执行',
`spring_id` varchar(255) DEFAULT NULL COMMENT 'springid',
`method_name` varchar(255) NOT NULL COMMENT '方法名',
PRIMARY KEY (`job_id`),
UNIQUE KEY `name_group` (`job_name`,`job_group`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=51 DEFAULT CHARSET=utf8;
2、task_schedule_time表,用来存储定时执行状态和日志,其中name和state设置成唯一索引,保证微服务中只有一个定时任务在执行。
CREATE TABLE `task_schedule_time` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(255) DEFAULT NULL COMMENT '任务名称',
`stime` datetime DEFAULT NULL COMMENT '开始时间',
`etime` datetime DEFAULT NULL COMMENT '结束时间',
`flag` varchar(255) DEFAULT NULL COMMENT '执行结果(S成功E失败)',
`state` varchar(20) DEFAULT NULL COMMENT '执行状态(0执行中)',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`,`state`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2552705 DEFAULT CHARSET=utf8;
二、项目集成
针对项目集成部分,task_schedule_job和task_schedule_time的增删改查就不再累赘编写。
1、增加quartz的依赖
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> </dependency>
2、创建要被执行的任务类,实现job接口中的excute方法,其中使用两种方式实现,无状态和有状态的定时任务。
无状态:不管上次定时任务是否执行完都再次执行
有状态:等待上次定时任务是否执行完都再次执行
public class QuartzJobFactory implements Job { public final Logger log = Logger.getLogger(this.getClass()); @Override public void execute(JobExecutionContext context) throws JobExecutionException { ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob"); TaskUtils.invokMethod(scheduleJob); } }
@DisallowConcurrentExecution
public class QuartzJobFactoryDisallowConcurrentExecution implements Job { public final Logger log = Logger.getLogger(this.getClass()); @Override public void execute(JobExecutionContext context) throws JobExecutionException { ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob"); TaskUtils.invokMethod(scheduleJob); } }
public class TaskUtils { public final static Logger log = Logger.getLogger(TaskUtils.class); /** * 通过反射调用scheduleJob中定义的方法 * * @param scheduleJob */ public static void invokMethod(ScheduleJob scheduleJob) { Object object = null; Class clazz = null; if (StringUtils.isNotBlank(scheduleJob.getSpringId())) { object = SpringContextHolder.getBean(scheduleJob.getSpringId()); } else if (StringUtils.isNotBlank(scheduleJob.getBeanClass())) { try { clazz = Class.forName(scheduleJob.getBeanClass()); object = clazz.newInstance(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (object == null) { log.error("任务名称 = [" + scheduleJob.getJobName() + "]---------------未启动成功,请检查是否配置正确!!!"); return; } clazz = object.getClass(); Method method = null; TaskTimeService taskTimeService = SpringContextHolder.getBean(TaskTimeService.class); try { method = clazz.getDeclaredMethod(scheduleJob.getMethodName()); } catch (NoSuchMethodException e) { log.error("任务名称 = [" + scheduleJob.getJobName() + "]---------------未启动成功,方法名设置错误!!!"); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (method != null) { if(!taskTimeService.getTaskJobState(scheduleJob.getJobName(),"0") && "1".equals(scheduleJob.getDescription())) {//如果此任务正在执行,则其他集群上服务不再执行此任务 return; } TaskScheduleTime taskScheduleTime = new TaskScheduleTime(); try { taskScheduleTime.setName(scheduleJob.getJobName()); taskScheduleTime.setStime(new Date()); if("1".equals(scheduleJob.getDescription())) { taskScheduleTime.setState("0"); } try { taskTimeService.addTime(taskScheduleTime); } catch (Exception e) { e.printStackTrace(); return; } method.invoke(object); taskScheduleTime.setEtime(new Date()); taskScheduleTime.setFlag("S"); taskScheduleTime.setState(null); taskTimeService.editTime(taskScheduleTime); } catch (Exception e) { log.error("任务名称 = [" + scheduleJob.getJobName() + "]调用过程中出现异常:", e); taskScheduleTime.setEtime(new Date()); taskScheduleTime.setFlag("F"); taskScheduleTime.setState(null); taskTimeService.editTime(taskScheduleTime); //jobExectErrorEmailNotice(scheduleJob, e.getMessage()); } } System.out.println("任务名称 = [" + scheduleJob.getJobName() + "]----------启动成功"); }
}
3、注入SchedulerFactoryBean
@Configuration public class TaskConfig { @Bean(name = "schedulerFactoryBean") public SchedulerFactoryBean getSchedulerFactoryBean(){ return new SchedulerFactoryBean(); } }
4、针对于job的操作和程序启动入口
@Service public class JobTaskService { public final Logger log = Logger.getLogger(this.getClass()); @Autowired private SchedulerFactoryBean schedulerFactoryBean; @Autowired private ScheduleJobDao scheduleJobDao; /** * 从数据库中取 区别于getAllJob * * @return */ public List<ScheduleJob> getAllTask() { return scheduleJobDao.getAll(); } /** * 添加到数据库中 区别于addJob */ public void addTask(ScheduleJob job) { job.setCreateTime(new Date()); scheduleJobDao.insertSelective(job); } /** * 从数据库中查询job */ public ScheduleJob getTaskById(Long jobId) { return scheduleJobDao.selectByPrimaryKey(jobId); } /** * 更改任务状态 * * @throws SchedulerException */ public void changeStatus(Long jobId, String cmd) throws SchedulerException { ScheduleJob job = getTaskById(jobId); if (job == null) { return; } if ("stop".equals(cmd)) { deleteJob(job); job.setJobStatus(ScheduleJob.STATUS_NOT_RUNNING); } else if ("start".equals(cmd)) { job.setJobStatus(ScheduleJob.STATUS_RUNNING); addJob(job); } scheduleJobDao.updateByPrimaryKeySelective(job); } /** * 更改任务 cron表达式 * * @throws SchedulerException */ public void updateCron(Long jobId, String cron) throws SchedulerException { ScheduleJob job = getTaskById(jobId); if (job == null) { return; } job.setCronExpression(cron); if (ScheduleJob.STATUS_RUNNING.equals(job.getJobStatus())) { updateJobCron(job); } scheduleJobDao.updateByPrimaryKeySelective(job); } /** * 添加任务 * @throws SchedulerException */ public void addJob(ScheduleJob job) throws SchedulerException { if (job == null || !ScheduleJob.STATUS_RUNNING.equals(job.getJobStatus())) { return; } Scheduler scheduler = schedulerFactoryBean.getScheduler(); log.debug(scheduler + ".......................................................................................add"); TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup()); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); // 不存在,创建一个 if (null == trigger) { Class clazz = ScheduleJob.CONCURRENT_IS.equals(job.getIsConcurrent()) ? QuartzJobFactory.class : QuartzJobFactoryDisallowConcurrentExecution.class; JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(job.getJobName(), job.getJobGroup()).build(); jobDetail.getJobDataMap().put("scheduleJob", job); CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup()).withSchedule(scheduleBuilder).build(); scheduler.scheduleJob(jobDetail, trigger); } else { // Trigger已存在,那么更新相应的定时设置 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); // 按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); // 按新的trigger重新设置job执行 scheduler.rescheduleJob(triggerKey, trigger); } } //@PostConstruct public void init(String group) throws Exception { String startSchedule = "true"; if ("true".equals(startSchedule)) { Scheduler scheduler = schedulerFactoryBean.getScheduler(); // 这里获取任务信息数据 List<ScheduleJob> jobList = scheduleJobDao.getAllByGroup(group); for (ScheduleJob job : jobList) { addJob(job); } } else { return; } } /** * 获取所有计划中的任务列表 * * @return * @throws SchedulerException */ public List<ScheduleJob> getAllJob() throws SchedulerException { Scheduler scheduler = schedulerFactoryBean.getScheduler(); GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup(); Set<JobKey> jobKeys = scheduler.getJobKeys(matcher); List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(); 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.setDescription("触发器:" + 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; } /** * 所有正在运行的job * * @return * @throws SchedulerException */ public List<ScheduleJob> getRunningJob() throws SchedulerException { Scheduler scheduler = schedulerFactoryBean.getScheduler(); List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs(); List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size()); for (JobExecutionContext executingJob : executingJobs) { ScheduleJob job = new ScheduleJob(); JobDetail jobDetail = executingJob.getJobDetail(); JobKey jobKey = jobDetail.getKey(); Trigger trigger = executingJob.getTrigger(); job.setJobName(jobKey.getName()); job.setJobGroup(jobKey.getGroup()); job.setDescription("触发器:" + 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; } /** * 暂停一个job * * @param scheduleJob * @throws SchedulerException */ public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException { Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.pauseJob(jobKey); } /** * 恢复一个job * * @param scheduleJob * @throws SchedulerException */ public void resumeJob(ScheduleJob scheduleJob) throws SchedulerException { Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.resumeJob(jobKey); } /** * 删除一个job * * @param scheduleJob * @throws SchedulerException */ public void deleteJob(ScheduleJob scheduleJob) throws SchedulerException { Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.deleteJob(jobKey); } /** * 立即执行job * * @param scheduleJob * @throws SchedulerException */ public void runAJobNow(ScheduleJob scheduleJob) throws SchedulerException { Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.triggerJob(jobKey); } /** * 更新job时间表达式 * * @param scheduleJob * @throws SchedulerException */ public void updateJobCron(ScheduleJob scheduleJob) throws SchedulerException { Scheduler scheduler = schedulerFactoryBean.getScheduler(); TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()); trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); scheduler.rescheduleJob(triggerKey, trigger); } public static void main(String[] args) { CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("xxxxx"); } }
5、关于定时任务状态和日志的操作类
@Service public class TaskTimeService{ @Autowired private TaskScheduleTimeDao taskScheduleTimeDao; public void addTime(TaskScheduleTime taskScheduleTime) { taskScheduleTimeDao.insertSelective(taskScheduleTime); } public void editTime(TaskScheduleTime taskScheduleTime) { taskScheduleTimeDao.updateByPrimaryKeySelective(taskScheduleTime); }
/** * 判断当前计划任务是否可执行 * true:可执行,false:不可执行 * @param name * @param state * @return */ public Boolean getTaskJobState(String name,String state) { Boolean flag = true; TaskScheduleTime taskScheduleTime = taskScheduleTimeDao.selectByNameAndState(name,state); if(taskScheduleTime != null) { flag = false; } return flag; }
}