在前篇介绍了简单的任务调度, 在这章中我将会介绍一款任务调度框架Quartz.
本次讲解Quartz是基于2.2.1版本
Quartz 介绍
在这里简单介绍一下Quartz。Quartz是java的开源的任务调度框架, 它包含几个重要的组件:
调度器(Scheduler)、触发器(Trigger)、作业(Job)
这些在后面会有介绍, 简而言之: scheduler相当于控制器, trigger相当于时间触发器, 而作业是对应的任务。
Quartz有什么好处
对于以下优点, 我可能说的不全, 只是自己的体会
- 任务Tigger能够被持久化, 这样即使在发布后, 任务依然能够执行, 而不需要重新设定。(非常爽)
- 能够轻松暂停恢复触发器(即下次不会被调度)。
- 支持Calander, Cron表达式等复杂的触发器,可以灵活的编写复杂触发器。
Quartz有什么不好的
因为是java的任务调度, 所以只支持java的任务调度(由于项目中还有python的相关业务需要任务调度, 只能寻找python的任务调度Celery.
Quartz的使用
下面我们就来使用以下Quartz。
Qaurtz的配置
在这里介绍Springboot与Quartz的配置, 这样我们可以不用写xml。
在这里说明一下, Quartz有2种模式, 集群和非集群, 集群允许一个服务挡掉后, 调度可以在集群其他服务上运行,使得容灾能力提高, 想象一下, 一个集群运行多个服务, 总不能每个服务都执行任务, 这样一次调度就会调用多次, 导致问题出现。
- 配置SchedulerFactoryBean
注意这里的DataSource采用自动注入的方式, 因为在真实开发中, 往往是多环境配置。
@Bean
public SchedulerFactoryBean quartzScheduler(DataSource quartzDataSource) throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setQuartzProperties(quartzProperties());
factory.setDataSource(quartzDataSource);
factory.setStartupDelay(60);
return factory;
}
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
//在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
quartz.properties相关配置如下, 可以参考。
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# threadCount和threadPriority将以setter的形式注入ThreadPool实例
# 并发个数
org.quartz.threadPool.threadCount = 10
# 优先级
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 60000
# 默认存储在内存中
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
这里不再配置连接池相关信息, 相关连接配置在application-env.properties进行配置。
说明一下SchedulerFactoryBean的作用: 加载配置信息生产SchedulerFactory, 然后用来生成调度器scheduler
- 配置scheduler
很简单, 用刚才生成的schedulerFactoryBean获得到scheduler.
@Bean(name="scheduler")
public Scheduler scheduler(SchedulerFactoryBean quartzScheduler) throws IOException {
return quartzScheduler.getScheduler();
}
Quartz的使用
- 创建任务Job
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(name, group).build();
// withIdentity
一个job通过identity识别唯一性, 不能存在2个相同identity的job。
如果group为null, 则group在数据库将是DEFAULT.
当然我们还可以设置job的其他属性, 如:
JobBuilder().storeDurably 如果设置为true,那么该任务会被保存到数据库中,否则, 在没有trigger关联的话就会被移除。
- 创建触发器
quartz提供好几种触发器(Tigger), trigger的类型主要由Scheduler体现。
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerName,triggerGroupName)
.withSchedule().startAt(time).forJob(job).build()
trigger也是通过Builder模式来创建。
同样,触发器是用来触发某个任务的执行的, 触发器关联着scheduler(控制器),控制器是真正控制触发器执行作业的。 在触发器中保存着什么时候任务将被执行, 上次执行的时间等。
- 创建schedule(注意不是scheduler),可以理解成计划
而scheduler是调度器, 用来执行计划。
calenderScheduler:
CalendarIntervalScheduleBuilder
.calendarIntervalSchedule()
. withInterval(timeInterval,unit)
.withMisfireHandlingInstructionFireAndProceed()
CronScheduleBuilder:
CronScheduleBuilder
.cronSchedule(cronExpression)
.withMisfireHandlingInstructionFireAndProceed()
注意withMisfireHandlingInstructionFireAndProceed 是由于一些原因(可能由于宕机等原因)任务该被执行却未被执行后的措施。是忽略还是执行。
DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule().endingDailyAt(time).build();
这个schedule 方便那些以在最后的多长时间执行。 比如每天的最后1个小时执行, 也就是23点执行。
- 将jobDetail和Trigger关联, 交给scheduler
scheduler.scheduleJob(jobDetail,trigger);
==关键点==
job是有JobBuilder.newJob(Clazz)来通过反射实例化的,所以在编写的job上加入@Component是无法通过spring扫描注入的。解决办法如下:
1. 通过实现ApplicationContextAware
将context注入。 然后通过
class MyJob implements Job, ApplicationContextAware{
protected static ApplicationContext context = null;
@Override
public void execute(final JobExecutionContext context) throws JobExecutionException {
...
});
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
}