SpringBoot-集成Quartz作业调度
在说 quartz 之前,我们先回顾一下 spring 的定时任务,使用相当简单,默认集成在 spring boot 中,所以在 spring boot 项目中无需额外添加依赖,无需配置,只需要加个注解就可以了,当然也可以实现动态添加删除定时任务,详情前往上一篇博文SpringBoot-定时任务,那为什么要使用 quartz 呢,主要还是考虑分布式的应用,下面我们就来看一下 spring boot 是怎么集成 quartz 的。
介绍
简单介绍下 quartz,Quartz是OpenSymphony开源组织在任务调度领域的一个开源项目,完全基于Java实现。作为一个优秀的开源调度框架,Quartz具有以下特点:
- (1)强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
- (2)灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
- (3)分布式和集群能力。
Tips
还记得 spring boot 的三板斧吗?加依赖,写配置,添注解
加依赖
引入 spring-boot-starter-quartz 的依赖1
2
3
4
5<!--quartz定时任务依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
写配置
无需配置
加注解
无需注解
实现
我们需要新建一个 QuartzService 的类,由于代码较长,在这里就不贴了,想看具体代码,请前往QuartzService.java。quartz 的 Job 实现方式很多,不一定要用这种方式,只是笔者认为这是一种比较简单的实现。
新加一个测试类 QuartzTest1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@SpringBootTest
public class QuartzTest {
private static final Logger logger = LoggerFactory.getLogger(QuartzTest.class);
@Autowired
private QuartzService quartzService;
@Test
public void quartzTest() {
logger.info("添加定时任务");
String jobName = "test-1";
Map<String, Object> map = new HashMap<>();
map.put("test", "测试任务执行");
map.put("name", jobName);
quartzService.deleteJob(jobName, "test");
quartzService.addJob(TestQuartz.class, jobName, "test", "0 */2 * * * ?", map);
}
}
新建 Job 类,需要继承 QuartzJobBean1
2
3
4
5
6
7
8
9
10
11
12public class TestQuartz extends QuartzJobBean {
private static final Logger logger = LoggerFactory.getLogger(TestQuartz.class);
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
logger.info("任务开始执行:" + formatter.format(System.currentTimeMillis()));
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
String test = jobDataMap.get("test").toString();
String jobName = jobDataMap.get("name").toString();
logger.info(test + ":" + jobName);
}
}
启动,运行 Test 类,便可添加一个任务,创建 Job 时需要的参数可以通过 JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
来获取,这样就完成了一个定时任务功能。
Quartz 集群使用
一个 Quartz 集群中的每个节点是一个独立的 Quartz 应用,它又管理着其他的节点。这就意味着你必须对每个节点分别启动或停止。Quartz 集群中,独立的 Quartz 节点并不与另一其的节点或是管理节点通信,而是通过相同的数据库表来感知到另一 Quartz 应用的。
因为 Quartz 集群依赖于数据库,所以必须首先创建 Quartz 数据库表,Quartz 发布包中包括了所有被支持的数据库平台的SQL脚本。这些SQL脚本存放于 <quartz_home>/docs/dbTables
目录下,总共12张表,不同版本,表个数可能不同。下面是具体表的说明:
- qrtz_blob_triggers : 以Blob 类型存储的触发器。
- qrtz_calendars:存放日历信息, quartz可配置一个日历来指定一个时间范围。
- qrtz_cron_triggers:存放cron类型的触发器。
- qrtz_fired_triggers:存放已触发的触发器。
- qrtz_job_details:存放一个jobDetail信息。
- qrtz_job_listeners:job监听器。
- qrtz_locks: 存储程序的悲观锁的信息(假如使用了悲观锁)。
- qrtz_paused_trigger_graps:存放暂停掉的触发器。
- qrtz_scheduler_state:调度器状态。
- qrtz_simple_triggers:简单触发器的信息。
- qrtz_trigger_listeners:触发器监听器。
- qrtz_triggers:触发器的基本信息。
接下来,新建 quartz.yml 的配置文件,来覆盖默认的配置。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27org:
quartz:
jobStore:
useProperties: false
tablePrefix: qrtz_
# 开启集群模式
isClustered: true
# 集群实例检测时间间隔 ms
clusterCheckinInterval: 5000
# misfire 任务的超时阈值 ms
misfireThreshold: 60000
txIsolationLevelReadCommitted: true
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
scheduler:
instanceId: AUTO
rmi.export: false
rmi.proxy: false
wrapJobExecutionInUserTransaction: false
# 工作线程的线程池设置
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 25
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
这样 Quartz 集群就可以实现了。
扩展
1、Quartz 触发时间配置的三种方式
- cron 方式:采用cronExpression表达式配置时间。
- simple 方式:和JavaTimer差不多,可以指定一个开始时间和结束时间外加一个循环时间。
- calendars 方式:可以和cron配合使用,用cron表达式指定一个触发时间规律,用calendar指定一个范围。
注意:cron方式需要用到的4张数据表: qrtz_triggers,qrtz_cron_triggers,qrtz_fired_triggers,qrtz_job_details
2、使用 quartz 遇到的问题
2.1、在定时任务执行中 service @Autowired 注解不进来
创建 JobFactory 的 Bean,并在 SchedulerConfig 中添加到 SchedulerFactoryBean1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// JobFactory
public class JobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
SchedulerConfig.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SchedulerConfig {
private DataSource dataSource;
private JobFactory jobFactory;
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.yml"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setSchedulerName("Cluster_Scheduler");
factory.setDataSource(dataSource);
factory.setApplicationContextSchedulerContextKey("applicationContext");
factory.setQuartzProperties(quartzProperties());
factory.setJobFactory(jobFactory);
return factory;
}
}
2.2、quartz 任务激活失败
在Quartz中,当一个持久化的触发器会因为:
- 调度器被关闭;
- 线程池没有可用线程;
- 项目重启;
- 任务的串行执行;
而错过激活时间,就会发生激活失败(misfire)。
可以设置 quartz中CornTrigger使用的策略1
2
3
4
5
6
7
8//所有的misfile任务马上执行
public static final int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;
//在Trigger中默认选择MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 策略
public static final int MISFIRE_INSTRUCTION_SMART_POLICY = 0;
// CornTrigger默认策略,合并部分misfire,正常执行下一个周期的任务。
public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
//所有的misFire都不管,执行下一个周期的任务。
public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;
1、 通过setMisfireInstruction方法设置misfire策略。1
2
3
4
5
6
7
8CronTriggerFactoryBean triggerFactoryBean = new CronTriggerFactoryBean();
triggerFactoryBean.setName("corn_" + clazzName);
triggerFactoryBean.setJobDetail(jobFactory.getObject());
triggerFactoryBean.setCronExpression(quartzCorn);
triggerFactoryBean.setGroup(QUARTZ_TRIGGER_GROUP);
//设置misfire策略
triggerFactoryBean.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY);
triggerFactoryBean.afterPropertiesSet();
2、 也可以通过CronScheduleBuilder设置misfire策略。1
2
3
4
5
6
7CronScheduleBuilder csb = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
//MISFIRE_INSTRUCTION_DO_NOTHING
csb.withMisfireHandlingInstructionDoNothing();
//MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
csb.withMisfireHandlingInstructionFireAndProceed();//(默认)
//MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
csb.withMisfireHandlingInstructionIgnoreMisfires();
最后
任务调度在实际项目中会经常用到,Quartz 也是我们的不二选择,但是在高可用的系统中也存在不少问题,具体问题小伙伴们可以在留言区留言,我们一起共同探讨。
参考与相关链接
示例代码:https://github.com/dddreams/learn-spring-boot/tree/master/spring-boot-quartz
Quartz 官网:http://www.quartz-scheduler.org/
Quartz集群原理及配置应用:https://www.cnblogs.com/xiang–liu/p/10120105.html
更多文章请关注微信公众号: zhiheng博客
如果文章对你有用,转发分享、点赞赞赏才是真爱 [斜眼笑]