一 什么是Quartz
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。官网地址:https://www.quartz-scheduler.org/
特点如下:
- 纯java实现,可以作为独立的应用程序,也可以嵌入在另一个独立式应用程序运行
强大的调度功能,Spring默认的调度框架,灵活可配置; - 作业持久化,调度环境持久化机制,可以保存并恢复调度现场。系统关闭数据不会丢失;灵活的应用方式,可以任意定义触发器的调度时间表,支持任务和调度各种组合,组件式监听器、各种插件、线程池等功能,多种存储方式等;
- 分布式和集群能力,可以被实例化,一个Quartz集群中的每个节点作为一个独立的Quartz使用,通过相同的数据库表来感知到另一个Quartz应用
二 内部实现
1.核心概念
-
Scheduler(任务调度器):实际执行任务调度的控制器,在spring中通过SchedulerFactoryBean封装起来
-
Trigger(触发器): 任务的触发,触发器有SimpleTrigger,CronTrigger,DateIntervalTrigger和NthIncludedDayTrigger四种类型,其中SimpleTrigger:能够周期性的设置任务触发;CronTrigger:使用cron表达式的方式实现任务触发,实现更多样,使用场景也是最多的。其中任务和触发器的关系是:一个任务可以有多个触发器,一个触发器只能对应一个任务。
-
JobDetail(定时任务的信息载体): 业务需要执行的任务操作,实现Job接口,或者继承QuartzJobBean,实现/重写里面的方法。
2.重要组成
- Job: 是一个接口只有一个方法,void execute(JobExecutionContext var1),JobExecutionContext这个类提供了调度上下文的各种信息,Job运行时的信息就保存在JobExecutionContext里的JobDataMap实例中。上文也提到了QuartzJobBean,QuartzJobBean跟Job一样也是用来实现自己的任务的,只不过QuartzJobBean是一个抽象类,用户可以继承该类去实现executeInternal这个抽象方法去实现自己的任务逻辑即可。
- JobDetail: Quartz在每次执行实例的时候都重新创建一个job实例,所以它不直接接受一个job实例,而是通过接受一个job实现类,以便运行时通过new Instance()的反射机制实例化job,因此需要通过一个类来描述job的实现类及其他相关静态信息,如job的名字,描述,关联监听器等信息。(即用来绑定job,并且在job执行的时候携带一些执行的信息)通过该类的构造函数可以更具体地了解它的功用:JobDetail(java.lang.String name, java.lang.String group, java.lang.Class jobClass),该构造函数要求指定Job的实现类,以及任务在Scheduler中的组名和Job名称;
- JobBuilder: 用来定义或者创建JobDetail的实例,JobDetail限定了只能是job的实例。
- JobStore: 接口,用来保存job数据,实现类主要有RAMJobStore,JobStoreTX,JobStoreCMT;
JobStoreTX和JobStoreCMT均将数据保存在数据库中,RAMJobStore将数据保存在内存中,保存一些执行的信息。 - Trigger: 一个类,描述触发的job执行时的时间触发规则;主要有SimpleTrigger和CronTrigger两个子类。当仅触发一次或者以固定时间间隔周期执行时,使用SimpleTrigger;CronTrigger通过cron表达式,定义出各种复杂时间规则的调度方案,如每天早晨的固定时间执行,或周二周三的固定时间执行等需求。
- TriggerBuilder: 使用builder模式,用来定义或者创建触发器的实例
- ThreadPool: Timer有且只有一个后台线程在执行,Quartz的schedule下有ThreadPool整个线程池来运行,schedule使用线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行的效率,从而解决并发问题.
- Scheduler: 调度器,代表Quartz的一个独立运行容器,Trigger和JobDetail可以注册Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例
- Calendar: 一个Trigger可以和多个Calendar关联,以排除或包含某些时间点。比如某个任务希望放假时间不执行。
- 监听器:JobListener,TriggerListener,SchedulerListener;分别对Job,Trigger,Scheduler的事件进行监听,包括scheduler一运行起来的时候,或者在执行任务的时候,或终止的时候进行监听,监听的时候加入一些自定义的某些逻辑,比如打印日志信息。
3.数据存储和持久化
为什么要持久化?
当程序突然被中断时,如断电,内存超出时,很有可能造成任务的丢失。 可以将调度信息存储到数据库里面,进行持久化,当程序被中断后,再次启动,仍然会保留中断之前的数据,继续执行,而并不是重新开始。
提供了两种存储方式:
- RAMJobStore:在默认情况下Quartz将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能,因为内存中数据访问最快。不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所有运行的信息都会丢失。
- JobStoreTX:所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务。
三 Springboot整合quartz
本示例:采用了springboot已经整合了quartz,如果用户是自己单独集成的,类似这样
可寻找其他示例。
1. 添加Quartz依赖
<!-- quartz 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
里面依赖了quartz的starter,以及mysql数据库,引入数据库的依赖目的是在于,后续quartz的job,trigger等,以及集群相关配置用到的锁,来控制任务集群模式下的重复执行等信息都是需要保存到数据库的,默认是放在内存中,内存中不利于管理,使用quartz的意义就不明显了。
2. mysql的建表sql:
#
# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#
#
# By: Ron Cordell - roncordell
# I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM.
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;
CREATE TABLE QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;
CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(190) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT