任务调度 Quartz
任务调度
任务调度需求
1、定义触发规则,基于时刻、时间间隔、表达式
2、定义任务
3、方便配置;配置文件/配置中心
4、任务串行;A-B-C
5、任务并发,互不干扰
6、调度器,启动、中断、停止任务
7、集成Spring
任务调度工具对比
层次 | 举例 | 特点 |
---|---|---|
操作系统 | Linux crontab Windows 计划任务 | 只能执行简单脚本或者命令 |
数据库 | MySQL、Oracle | 可以操作数据。不能执行Java 代码 |
工具 | Kettle | 可以操作数据,执行脚本。没有集中配置 |
开发语言 | JDK Timer、ScheduledThreadPool | Timer:单线程 JDK1.5 之后:ScheduledThreadPool(Cache、Fiexed、Single):没有集中配置,日程管理不够灵活 |
容器 | Spring Task、@Scheduled | 不支持集群 |
分布式框架 | XXL-JOB,Elastic-Job |
Quartz
api
依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
默认配置文件
quartz.properties
包自带的,如果我们没有创建该配置文件,就用系统自带的
创建Job
public class MyJob implements Job
{
@Override
public void execute(JobExecutionContext context) throws JobExecutionException
{
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(format.format(date));
}
}
Job 包装成JobDetail
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "group1")
.usingJobData("name","2673")
.usingJobData("moon",5.21F)
.build();
创建触发器Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
创建调度器 Scheduler
调度器一定都是单例
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
JobDetail
是由Job包装而来
Trigger
JobDetail 和Trigger 比例是1比N
Trigger有4种子接口
子接口 | 描述 | 特点 |
---|---|---|
SimpleTrigger | 简单触发器 | 固定时刻或时间间隔,毫秒 |
CalendarIntervalTrigger | 基于日历的触发器 | 比简单触发器更多时间单位,支持非固定时间的触发,例如一年可365/366,一个月可能28/29/30/31 |
DailyTimeIntervalTrigger | 基于日期的触发器 | 每天的某个时间段 |
CronTrigger | 基于Cron 表达式的触发器 |
Calendar 的排除规则
名称 | 用法 |
---|---|
BaseCalendar | 为高级的Calendar 实现了基本的功能,实现了org.quartz.Calendar 接口 |
AnnualCalendar | 排除年中一天或多天 |
CronCalendar | 日历的这种实现排除了由给定的CronExpression 表达的时间集合。例如,您可以使用此日历使用表达式“* * 0-7,18-23?* *”每天排除所有营业时间(上午8 点至下午5 点)。如果CronTrigger 具有给定的cron 表达式并且与具有相同表达式的CronCalendar 相关联,则日历将排除触发器包含的所有时间,并且它们将彼此抵消。 |
DailyCalendar | 您可以使用此日历来排除营业时间(上午8 点- 5 点)每天。每个DailyCalendar 仅允许指定单个时间范围,并且该时间范围可能不会跨越每日边界(即,您不能指定从上午8 点至凌晨5 点的时间范围)。如果属性invertTimeRange 为false(默认),则时间范围定义触发器不允许触发的时间范围。如果invertTimeRange 为true,则时间范围被反转- 也就是排除在定义的时间范围之外的所有时间。 |
HolidayCalendar | 特别的用于从Trigger 中排除节假日 |
MonthlyCalendar | 排除月份中的指定数天,例如,可用于排除每月的最后一天 |
WeeklyCalendar | 排除星期中的任意周几,例如,可用于排除周末,默认周六和周日 |
Scheduler 调度器
由 StdSchedulerFactory 产生的,是单例
scheduler的方法主要有三类:
1、操作调度器本身,start、shutdown
2、操作trigger pauseTriggers()、resumeTrigger()
3、操作job ;scheduleJob()、unscheduleJob()、rescheduleJob()
Listener
针对job、调度器、触发器的三种监听
JobListener
方法 | 作用 |
---|---|
getName() | 返回JobListener 的名称 |
jobToBeExecuted() | Scheduler 在JobDetail 将要被执行时调用这个方法 |
jobExecutionVetoed() | Scheduler 在JobDetail 即将被执行,但又被TriggerListener 否决了时调用这个方法 |
jobWasExecuted() | Scheduler 在JobDetail 被执行之后调用这个方法 |
TriggerListener
方法 | 作用 |
---|---|
getName() | 返回监听器的名称 |
triggerFired() | Trigger 被触发,Job 上的execute() 方法将要被执行时,Scheduler 就调用这个方法 |
vetoJobExecution() | 在Trigger 触发后, Job 将要被执行时由Scheduler 调用这个方法。TriggerListener 给了一个选择去否决Job 的执行。假如这个方法返回true,这个Job 将不会为此次Trigger 触发而得到执行 |
triggerMisfired() | Trigger 错过触发时调用 |
triggerComplete() | Trigger 被触发并且完成了Job 的执行时,Scheduler 调用这个方法 |
SchedulerListener
方法太多
Spring 集成Quartz
把job、Trigger、scheduler定义成Bean
Job
<bean name="myJob1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="my_job_1"/>
<property name="group" value="my_group"/>
<property name="jobClass" value="com.gupaoedu.quartz.MyJob1"/>
<property name="durability" value="true"/>
</bean>
Trigger
<bean name="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="name" value="my_trigger_1"/>
<property name="group" value="my_group"/>
<property name="jobDetail" ref="myJob1"/>
<property name="startDelay" value="1000"/>
<property name="repeatInterval" value="5000"/>
<property name="repeatCount" value="2"/>
</bean>
Scheduler
<bean name="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="simpleTrigger"/>
<ref bean="cronTrigger"/>
</list>
</property>
</bean>
Test
public class QuartzTest {
private static Scheduler scheduler;
public static void main(String[] args) throws SchedulerException {
// 获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("spring_quartz.xml");
// 从容器中获取调度器
scheduler = (StdScheduler) ac.getBean("scheduler");
// 启动调度器
scheduler.start();
}
}
注解方式配置
@Configuration
public class QuartzConfig
{
@Bean
public JobDetail printTimeJobDetail()
{
return JobBuilder.newJob(MyJob3.class).withIdentity("gupaoJob").usingJobData("gupao", "职位更好的你").storeDurably().build();
}
@Bean
public Trigger printTimeJobTrigger()
{
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
return TriggerBuilder.newTrigger().forJob(printTimeJobDetail()).withIdentity("quartzTaskService").withSchedule(cronScheduleBuilder).build();
}
@Bean
public SchedulerFactoryBean printTimeJobScheduler()
{
SchedulerFactoryBean bean = new SchedulerFactoryBean();
bean.setStartupDelay(1);
return bean;
}
}
测试
ApplicationContext context = new AnnotationConfigApplicationContext(QuartzConfig.class);
JobDetail jobDetail = (JobDetail) context.getBean(JobDetail.class);
Trigger trigger = (Trigger) context.getBean(Trigger.class);
scheduler = (Scheduler) context.getBean(Scheduler.class);
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
SchedulerFactoryBean 实例化是内部会创建一个scheduler 对象。
Springboot集成quartz
任务配置管理
表JobDetail
数据操作与任务调度
public class SchedulerUtil {
private static Logger logger = LoggerFactory.getLogger(SchedulerUtil.class);
/**
* 新增定时任务
* @param jobClassName 类路径
* @param jobName 任务名称
* @param jobGroupName 组别
* @param cronExpression Cron表达式
* @param jobDataMap 需要传递的参数
* @throws Exception
*/
public static void addJob(String jobClassName,String jobName, String jobGroupName, String cronExpression,String jobDataMap) throws Exception {
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
// 启动调度器
scheduler.start();
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass())
.withIdentity(jobName, jobGroupName).build();
// JobDataMap用于传递任务运行时的参数,比如定时发送邮件,可以用json形式存储收件人等等信息
if (StringUtils.isNotEmpty(jobDataMap)) {
JSONObject jb = JSONObject.parseObject(jobDataMap);
Map<String, Object> dataMap =(Map<String, Object>) jb.get("data");
for (Map.Entry<String, Object> m:dataMap.entrySet()) {
jobDetail.getJobDataMap().put(m.getKey(),m.getValue());
}
}
// 表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
.withSchedule(scheduleBuilder).startNow().build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
logger.info("创建定时任务失败" + e);
throw new Exception("创建定时任务失败");
}
}
/**
* 停用一个定时任务
* @param jobName 任务名称
* @param jobGroupName 组别
* @throws Exception
*/
public static void jobPause(String jobName, String jobGroupName) throws Exception {
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
scheduler.pauseJob(JobKey.jobKey(jobName, jobGroupName));
}
/**
* 启用一个定时任务
* @param jobName 任务名称
* @param jobGroupName 组别
* @throws Exception
*/
public static void jobresume(String jobName, String jobGroupName) throws Exception {
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
scheduler.resumeJob(JobKey.jobKey(jobName, jobGroupName));
}
/**
* 删除一个定时任务
* @param jobName 任务名称
* @param jobGroupName 组别
* @throws Exception
*/
public static void jobdelete(String jobName, String jobGroupName) throws Exception {
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
scheduler.pauseTrigger(TriggerKey.triggerKey(jobName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));
}
/**
* 更新定时任务表达式
* @param jobName 任务名称
* @param jobGroupName 组别
* @param cronExpression Cron表达式
* @throws Exception
*/
public static void jobReschedule(String jobName, String jobGroupName, String cronExpression) throws Exception {
try {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).startNow().build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
System.out.println("更新定时任务失败" + e);
throw new Exception("更新定时任务失败");
}
}
/**
* 检查Job是否存在
* @throws Exception
*/
public static Boolean isResume(String jobName, String jobGroupName) throws Exception {
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
Boolean state = scheduler.checkExists(triggerKey);
return state;
}
/**
* 暂停所有任务
* @throws Exception
*/
public static void pauseAlljob() throws Exception {
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
scheduler.pauseAll();
}
/**
* 唤醒所有任务
* @throws Exception
*/
public static void resumeAlljob() throws Exception {
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
sched.resumeAll();
}
/**
* 获取Job实例
* @param classname
* @return
* @throws Exception
*/
public static BaseJob getClass(String classname) throws Exception {
try {
Class<?> c = Class.forName(classname);
return (BaseJob) c.newInstance();
} catch (Exception e) {
throw new Exception("类["+classname+"]不存在!");
}
}
}
前端界面
容器启动与Service 注入
任务定义在数据库,容器启动如何读取任务
利用CommandLineRunner接口,实现项目启动后读取job构建定时任务。
@Component
public class InitStartSchedule implements CommandLineRunner
{
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ISysJobService sysJobService;
@Autowired
private MyJobFactory myJobFactory;
@Override
public void run(String... args) throws Exception
{
/**
* 用于程序启动时加载定时任务,并执行已启动的定时任务(只会执行一次,在程序启动完执行)
*/
//查询job状态为启用的
HashMap<String, String> map = new HashMap<String, String>();
map.put("jobStatus", "1");
List<SysJob> jobList = sysJobService.querySysJobList(map);
if (null == jobList || jobList.size() == 0)
{
logger.info("系统启动,没有需要执行的任务... ...");
}
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
// 如果不设置JobFactory,Service注入到Job会报空指针
scheduler.setJobFactory(myJobFactory);
// 启动调度器
scheduler.start();
for (SysJob sysJob : jobList)
{
String jobClassName = sysJob.getJobName();
String jobGroupName = sysJob.getJobGroup();
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(sysJob.getJobClassPath()).getClass()).withIdentity(jobClassName, jobGroupName).build();
if (StringUtils.isNotEmpty(sysJob.getJobDataMap()))
{
JSONObject jb = JSONObject.parseObject(sysJob.getJobDataMap());
Map<String, Object> dataMap = (Map<String, Object>) jb.get("data");
for (Map.Entry<String, Object> m : dataMap.entrySet())
{
jobDetail.getJobDataMap().put(m.getKey(), m.getValue());
}
}
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(sysJob.getJobCron());
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName).withSchedule(scheduleBuilder).startNow().build();
// 任务不存在的时候才添加
if (!scheduler.checkExists(jobDetail.getKey()))
{
try
{
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e)
{
logger.info("\n创建定时任务失败" + e);
throw new Exception("创建定时任务失败");
}
}
}
}
public static BaseJob getClass(String classname) throws Exception
{
Class<?> c = Class.forName(classname);
return (BaseJob) c.newInstance();
}
}
Service 类注入到Job 中
Job实例是quartz管理的,Job中的service是spring管理的。Job中依赖service会空指针
利用 AutowireCapableBeanFactory 把 job实例注册到spring中
@Component
public class MyJobFactory extends AdaptableJobFactory
{
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception
{
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
指定Scheduler 的JobFactory 为自定义的JobFactory
这里有个问题,貌似定时任务执行时都会创建一个Job实例。
Quartz 调度原理
获取调度器实例
入口从Scheduler scheduler = factory.getScheduler();进去
instantiate()负责初始化从工作,如果我们没有创建quartz.properties配置文件,
就加载默认的文件 org/quartz/quartz.properties
// 存储任务信息的JobStore
JobStore js = null;
// 创建线程池,默认是SimpleThreadPool
ThreadPool tp = null;
// 创建调度器
QuartzScheduler qs = null;
// 连接数据库的连接管理器
DBConnectionManager dbMgr = null;
// 自动生成ID
// 创建线程执行器,默认为DefaultThreadExecutor
ThreadExecutor threadExecutor;
创建线程池(包工头)
SimpleThreadPool里维护了三个List,分别存放所有工作线程、控线工作线程、忙碌工作线程
runInThread()是线程池运行线程的方法,参数runnable是要运行的内容
取出WorkerThread 去执行参数里面的runnable(JobRunShell)
WorkerThread(工人)
WorkerThread 是SimpleThreadPool 的内部类,用来执行任务。我们把
WorkerThread 理解为工人。在WorkerThread 的run 方法中,执行传入的参数runnable任务:
创建调度线程(项目经理)
QuartzScheduler qs = null;
qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
在QuartzScheduler 的构造函数中,创建了QuartzSchedulerThread,我们把它
理解为项目经理,它会调用包工头的工人资源,给他们安排任务。
并且创建了线程执行器schedThreadExecutor , 执行了这个
QuartzSchedulerThread,也就是调用了它的run 方法。
点开QuartzSchedulerThread 类,找到run 方法,这个是Quartz 任务调度的核心方法:
JobRunShell 的作用
JobRunShell 用来为Job 提供安全的运行环境的,执行Job 中所有的作业,捕获运行中的异常,在任务执行完毕的
时候更新Trigger 状态,等等。
JobRunShell 实例是用JobRunShellFactory 为QuartzSchedulerThread 创建的,在调度器决定一个Job 被触发的时候,
它从线程池中取出一个线程来执行任务。
绑定JobDetail 和Trigger
启动调度器
// 通知监听器
notifySchedulerListenersStarting();
if (initialStart == null) {
initialStart = new Date();
this.resources.getJobStore().schedulerStarted();
startPlugins();
} else {
resources.getJobStore().schedulerResumed();
}
// 通知QuartzSchedulerThread 不再等待,开始干活
schedThread.togglePause(false);
// 通知监听器
notifySchedulerListenersStarted();
源码总结
getScheduler 方法创建线程池ThreadPool,创建调度器QuartzScheduler,创建
调度线程QuartzSchedulerThread,调度线程初始处于暂停状态。
scheduleJob 将任务添加到JobStore 中。
scheduler.start()方法激活调度器,QuartzSchedulerThread 从timeTrriger 取出待
触发的任务, 并包装成TriggerFiredBundle , 然后由JobRunShellFactory 创建
TriggerFiredBundle 的执行线程JobRunShell , 调度执行通过线程池
SimpleThreadPool 去执行JobRunShell,而JobRunShell 执行的就是任务类的execute
方法:job.execute(JobExecutionContext context)。