Quartz简介
Quartz是一个功能丰富的开源作业调度库,几乎可以集成在任何Java应用程序中 - 从最小的独立应用程序到最大的电子商务系统。Quartz可用于创建简单或复杂的计划,以执行数十,数百甚至数万个作业; 将任务定义为标准Java组件的作业,这些组件可以执行几乎任何可以编程的程序。Quartz Scheduler包含许多企业级功能,例如支持JTA事务和集群。
为什么不简单的使用java.util.Timer就行了呢?
从JDK1.3开始,Java通过java.util.Timer和java.util.TimerTask可以实现定时器。列举其中部分原因:
Timers没有持久化机制。
Timers不灵活 (只可以设置开始时间和重复间隔,不是基于时间、日期、天等(秒、分、时)的)。
Timers 不能利用线程池,一个timer一个线程。
Timers没有真正的管理计划
Quartz 和 Spring Task使用比较
Spring Quartz 特点: 1. 默认多线程异步执行
2. 单任务同步:配置属性,可以使一个任务的一次调度在未完成时,而不会开启下一次调度 3. 多个任务同时运行,任务之间没有直接的影响,触发器和任务解耦
Spring Task特点:
1. 默认单线程同步执行
2. 一个任务执行完上一次之后,才会执行下一次调度
3. 多任务之间按顺序执行,一个任务执行完成之后才会执行另一个任务
4. 多任务并行执行需要设置线程池
5. 全程可以通过注解配置
Quartz体系结构
Quartz的工作原理:
Quartz是通过SchedulerFactory工厂生成Scheduler来进行任务的操作,Scheduler可以把任务JobDetail和触发器Trigger加入任务池中,可以删除或暂停任务等,Scheduler把这些任务和触发器放到一个JobStore中,这里JobStore有内存形式的也有持久化形式的,当然也可以自定义扩展成独立的服务。
Scheduler内部会通过一个调度线程QuartzSchedulerThread,不断到JobStore中找出下次需要执行的任务,并把这些任务封装放到一个线程池ThreadPool中运行。
Scheduler 调度线程主要有两个: 执行常规调度的线程(QuartzSchedulerThread),和执行 misfired trigger 的线程(MisfireHandler类,是JobStoreSupport内部线程类)。常规调度线程轮询存储的所有 trigger,如果有需要触发的 trigger,即到达了下一次触发的时间,则从任务执行线程池获取一个空闲线程,执行与该 trigger 关联的任务。Misfire 线程是扫描所有的 trigger,查看是否有 misfired trigger,如果有的话根据 misfire 的策略分别处理。
Quartz有调度器、任务和触发器这三个核心概念,其中的核心接口和类如下:
Scheduler :这是Quartz Scheduler的主要接口,代表一个独立运行容器。调度程序维护JobDetail和Trigger的注册表。 一旦在Scheduler注册过了,当定时任务触发时间一到,调度程序就会负责执行预先定义的Job。
SchedulerFactory :调度程序Scheduler实例是通过SchedulerFactory工厂来创建的。
Trigger :具有所有触发器通用属性的基本接口,描述了job执行的时间出发规则。 - 使用TriggerBuilder实例化实际触发器。
Job:要由表示要执行的“作业”的类实现的接口。只有一个方法 void execute(jobExecutionContext context) (jobExecutionContext 提供调度上下文各种信息,运行时数据保存在jobDataMap中) Job有个子接口StatefulJob ,代表有状态任务。 有状态任务不可并发,前次任务没有执行完,后面任务处于阻塞等到。
JobDetail :传递给定任务实例的详细信息属性。 JobDetail将使用JobBuilder创建/定义。
JobStore: 为org.quartz.core.QuartzScheduler的使用提供一个org.quartz.Job和org.quartz.Trigger存储机制。作业和触发器的存储应该以其名称和组的组合为唯一性。
QuartzScheduler :这是Quartz的核心,它是org.quartz.Scheduler接口的间接实现,包含调度org.quartz.Jobs,注册org.quartz.JobListener实例等的方法。
QuartzSchedulerThread :负责执行向QuartzScheduler注册的触发Trigger的工作的线程。
ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提供运行效率。
QuartzSchedulerResources:包含创建QuartzScheduler实例所需的所有资源(JobStore,ThreadPool等)。
Quartz的应用场景
简捷地实现简单或复杂的计划,完成一系列定时任务,周期性任务的管理,可以选择持久化,记录任务的执行情况,根据任务无状态或有状态接口的不同实现定制计划。无状态任务一般指可以并发的任务,即任务之间是独立的,不会互相干扰,有状态任务来说,是不能并发执行的,同一时间只能有一个任务在执行。对此如果需要 misfired job,Quartz 会查看其 misfire 策略是如何设定的,如果是立刻执行,则会马上启动一次执行,如果是等待下次执行,则会忽略错过的任务,而等待下次触发执行。Quartz也支持集群和分布式任务。
Quartz集群基于数据库锁,一个调度器实例在执行涉及到分布式问题的数据库操作前,首先要获取QUARTZ_LOCKS表中对应的行级锁,获取锁后即可执行其他表中的数据库操作,随着操作事务的提交,行级锁被释放,供其他调度实例获取。集群中的每一个调度器实例都遵循这样一种严格的操作规程。
Quartz和LTS的分布式集群对比
1. Quartz不能很好的解决故障转移
2. Quartz 是通过数据库行级锁的方式实现多线程之间任务争用的问题。行锁开销大,加锁慢, 会出现死锁,并发度相比表级锁,页级锁高一点。但是在任务量比较大的时候,并发度较大的时候,行级锁就显得比较吃力了,而且很容易发生死锁。LTS采用预加载和乐观锁的方式,批量的将部分要执行的任务预加载到内存中。
3. LTS 支持Failstore ,任务提交失败了,自动帮你本地持久化, Quartz当前节点挂了,内存中的数据就会全部丢失了
项目案例
项目通过springboot +quartz + mysql 实现动态任务添加,暂停,删除,完成一个简单的入门案例。
开发环境:JDK1.8,MAVEN,IDEA,Mysql 5.7
到官网下载对应版本的Quartz源码包,运行对应数据库的sql语句
数据库表详解:
1. pom.xml引入quartz依赖
<!--spring quartz依赖-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.3</version>
</dependency>
2. 简单配置quartz.properties文件
#默认或是自己改名字都行
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
#如果使用集群,instanceId必须唯一,设置成AUTO
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000
#============================================================================
# Configure JobStore
#============================================================================
#
#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
#存储方式使用JobStoreTX,也就是数据库
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#使用自己的配置文件
org.quartz.jobStore.useProperties:true
#数据库中quartz表的表名前缀
org.quartz.jobStore.tablePrefix:QRTZ_
org.quartz.jobStore.dataSource:qzDS
#是否使用集群(如果项目只部署到 一台服务器,就不用了)
#org.quartz.jobStore.isClustered = true
#============================================================================
# Configure Datasources
#============================================================================
#配置数据源
org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf8
org.quartz.dataSource.qzDS.user:root
org.quartz.dataSource.qzDS.password:123456
org.quartz.dataSource.qzDS.validationQuery=select 0 from dual
配置文件设置调度程序scheduler的名称和示例id(id也可以设置AUTO)
设置工作线程线程数
指定所有Quartz的数据的存储方式。通过使用一个数据库,你可以打开一个全新的维度。
3. 创建job 实例工厂
@Component
public class MyJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
4. quartz的初始化配置
@Configuration
public class SchedulerConfiguration {
@Autowired
private MyJobFactory myJobFactory;
@Bean(name = "schedulerFactoryBean")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
//获取配置属性
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
//在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
//创建SchedulerFactoryBean
SchedulerFactoryBean factory = new SchedulerFactoryBean();
Properties pro = propertiesFactoryBean.getObject();
factory.setOverwriteExistingJobs(true);
factory.setAutoStartup(true);
factory.setQuartzProperties(pro);
factory.setJobFactory(myJobFactory);
return factory;
}
}
4. 新建service接口和实现类
/**
* 添加一个定时任务
* @param jobName
* @param jobGroup
*/
void addCronJob(String jobName, String jobGroup);
/**
* 添加异步任务
* @param jobName
* @param jobGroup
*/
void addAsyncJob(String jobName, String jobGroup);
/**
* 暂停任务
* @param jobName
* @param jobGroup
*/
void pauseJob(String jobName, String jobGroup);
/**
* 恢复任务
* @param triggerName
* @param triggerGroup
*/
void resumeJob(String triggerName, String triggerGroup);
/**
* 删除job
* @param jobName
* @param jobGroup
*/
void deleteJob(String jobName, String jobGroup);
5. 新建控制器
RestController
@RequestMapping("/quartztest")
public class JobController {
@Autowired
private JobService jobService;
/**
* 创建cron任务
* @param jobName
* @param jobGroup
* @return
*/
@RequestMapping(value = "/cron",method = RequestMethod.POST)
public String startCronJob(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup){
jobService.addCronJob(jobName,jobGroup);
return "create cron task success";
}
/**
* 创建异步任务
* @param jobName
* @param jobGroup
* @return
*/
@RequestMapping(value = "/async",method = RequestMethod.POST)
public String startAsyncJob(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup){
jobService.addAsyncJob(jobName,jobGroup);
return "create async task success";
}
/**
* 暂停任务
* @param jobName
* @param jobGroup
* @return
*/
@RequestMapping(value = "/pause",method = RequestMethod.POST)
public String pauseJob(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup){
jobService.pauseJob(jobName,jobGroup);
return "pause job success";
}
/**
* 恢复任务
* @param jobName
* @param jobGroup
* @return
*/
@RequestMapping(value = "/resume",method = RequestMethod.POST)
public String resumeJob(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup){
jobService.resumeJob(jobName,jobGroup);
return "resume job success";
}
/**
* 删除务
* @param jobName
* @param jobGroup
* @return
*/
@RequestMapping(value = "/delete",method = RequestMethod.PUT)
public String deleteJob(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup){
jobService.deleteJob(jobName,jobGroup);
return "delete job success";
}
}
6.运行结果