翻翻Quartz框架的旧账
前言
Quartz是一个基于Java开发的 分布式定时任务框架,在灵活且强大的任务调度能力的基础上还具备分布式能力。本文将对这个经典且好用的定时任务框架进行概念入门,并给出基于原生Quartz框架的示例工程。
正文
一. Quartz基本概念
Quartz的核心功能就是管理调度 定时任务并执行。在Quartz中,任务由Job组件表示,代表要定时执行的业务逻辑,而 定时则由Trigger组件表示,代表定时的时间规则,除此之外,还有一个组件叫做Scheduler负责全局的Job和Trigger的管理调度。
所以,Quartz中,核心的组件就是Job,Trigger和Scheduler,这三者关系可以用下图进行示意。
特别要注意,一个Job可以被多个Trigger所关联,但一个Trigger只能关联一个Job。
围绕Job,Trigger和Scheduler,Quartz构建了如下的一套完整的定时任务架构体系。
下面分别介绍一下上图中出现的组件。
- Job。表示定时任务的具体的任务内容,也就是 要作什么。Quartz中提供了Job接口,我们定义的任务需要实现Job接口;
- JobDetail。JobDetail有一个jobClass的类字段,表示其绑定的Job的类对象,此外JobDetail在Job之上扩展提供了很多属性,其中很重要的就是JobDetail有name和group字段来唯一标识自己,因此Quartz中在存储和调度任务时,实际的操作对象是JobDetail;
- JobBuilder。用于创建JobDetail;
- Trigger。表示任务的定时规则,称为触发器,也就是 什么时候做,做多少次。Quartz中定义了Trigger接口,Trigger接口要求所有的触发器,都需要有 开始时间和 结束时间,也就是起止时间是需要有的,此外Trigger接口有两个常用的子接口,分别为SimpleTrigger和CronTrigger,其中SimpleTrigger在Trigger基础上,可以额外指定 执行次数和 相邻执行的间隔来决定定时规则,而CronTrigger在Trigger基础上可以额外指定cron表达式来决定定时规则;
- TriggerBuilder。用于创建Trigger;
- Scheduler和SchedulerFactory。SchedulerFactory用于创建Scheduler,Scheduler是一个接口,其有三个实现,分别是JBoss4RMIRemoteMBeanScheduler,RemoteScheduler和StdScheduler,常用的是StdScheduler,但无论是哪种Scheduler其实都不重要,因为Scheduler的三个实现其实都是一个壳子,真正的管理调度是交给其持有的QuartzScheduler来完成;
- QuartzScheduler。真正的调度器,用于管理调度JobDetail和Trigger。所谓管理,QuartzScheduler可以完成JobDetail和Trigger的 增删改查,所谓调度,通过QuartzScheduler可以决定在哪个实例上触发哪些Trigger来执行哪些定时任务;
- ThreadPool。由QuartzScheduler持有的一个线程池。当某一个定时任务满足条件要执行了,调度器会为这个定时任务从ThreadPool中分配一个线程来执行这个任务;
- JobStore。决定Quartz中的JobDetail和Trigger如何存储。JobStore接口有两个常用实现,分别是JobStoreSupport和RAMJobStore,其中JobStoreSupport会将JobDetail和Trigger存储到数据库,RAMJobStore会将JobDetail和Trigger存储到内存;
- QuartzSchedulerThread。由QuartzScheduler持有的 调度线程。在QuartzScheduler创建出来并被调用start() 方法后,QuartzSchedulerThread就会开始运行,会不断的去判断哪些Trigger到点需要触发了,需要触发的Trigger就会被从ThreadPool中分配一个线程,然后执行Trigger关联的JobDetail;
- listeners。是Quartz提供的监听器机制,具体有三种监听器,分别为JobListener,TriggerListener和SchedulerListener,其中常用的是JobListener。
最后说明一下JobDetail和Trigger的关系。在Quartz中,JobDetail和Trigger都有name和group这两个字段,这两个字段就能唯一标识一个JobDetail或Trigger,此时Trigger会通过JobDetail的name和group来关联一个JobDetail,当Trigger的定时规则满足时,Trigger就会触发,此时Trigger关联的任务就会被执行,此时也称任务满足定时条件被执行了。
二. Quartz的使用场景
说到定时任务,我们首先会想到使用ScheduledThreadPoolExecutor,或者使用Spring的@Scheduled注解,这种场景下,定时任务的执行是单点的,定时任务的信息是在内存中的,Quartz的最简单的使用场景就是这种场景。
如果再进阶一点,我们可以使用Quartz将定时任务的信息进行持久化,就算实例发生故障重启,也不会造成定时任务信息丢失,此时定时任务的执行仍旧是单点的,但是定时任务的信息却完成了持久化。
上述两种场景其实在生产中不怎么使用,使用Quartz更多的是想要实现 应用多实例部署时的定时任务单点执行。什么意思呢,意思就是应用如果有多个实例,那么当定时任务要执行时,仅由多个实例中的其中一个实例来执行任务,这样一来可以防止定时任务重复执行,二来可以防止单点故障耽搁定时任务的执行。此外,Quartz在此基础上,还能提供多实例下的定时任务启停,misfire补偿等额外功能。
三. Quartz的使用入门
本文将基于原生Quartz来演示多实例时的定时任务执行。
首先在pom中引入如下依赖。
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
然后创建一个Job接口的实现类。
public class HelloJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println(LocalDateTime.now().toString() + " Hello");
}
}
如下测试方法完成JobDetail和Trigger的创建和注册。
public class AddJobAndTrigger {
public static final String JOB_NAME_1 = "Job-Name-1";
public static final String JOB_GROUP_1 = "Job-Group-1";
public static final String TRIGGER_NAME_1 = "Trigger-Name-1";
public static final String TRIGGER_GROUP_1 = "Trigger-Group-1";
public static void main(String[] args) throws Exception {
// 创建调度器
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 创建JobDetail
JobDetail jobDetail = JobBuilder
.newJob(HelloJob.class)
.withIdentity(JOB_NAME_1, JOB_GROUP_1)
.build();
// 创建Trigger
Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(TRIGGER_NAME_1, TRIGGER_GROUP_1)
.startNow()
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever())
.build();
// 注册JobDetail和Trigger
scheduler.scheduleJob(jobDetail, trigger);
}
}
如下测试方法开启调度器。
public class ClusterTest {
public static void main(String[] args) throws Exception {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.start();
}
}
因为定时任务信息要持久化到数据库且要实现多实例调度,所以还需要添加如下的配置文件,配置文件名字需要是quartz.properties并放在classpath下,Quartz框架会自己去读取。
# 区分不同的调度器
# 该配置相同的实例才会进行分布式定时调度
org.quartz.scheduler.instanceName = MyScheduler
# 自动设置实例ID
org.quartz.scheduler.instanceId = AUTO
# 数据保存方式设置为持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 数据源名
org.quartz.jobStore.dataSource = quartzDataSource
# 是否分布式化
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 5000
# 线程池配置
org.quartz.threadPool.threadCount = 3
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 配置数据源信息
org.quartz.dataSource.quartzDataSource.driver = com.mysql.cj.jdbc.Driver
org.quartz.dataSource.quartzDataSource.URL = 数据库连接串
org.quartz.dataSource.quartzDataSource.user = 数据库用户名
org.quartz.dataSource.quartzDataSource.password = 数据库用户密码
org.quartz.dataSource.quartzDataSource.maxConnections = 5
最后在源码的org.quartz.impl.jdbcjobstore目录下找到tables_mysql_innodb.sql并执行,为数据库添加Quartz框架需要的表。
此时先运行AddJobAndTrigger测试程序,完成JobDetail和Trigger的注册,然后运行ClusterTest测试程序,开启实际的任务调度并执行。
总结
Quartz的核心就是任务Job,触发器Trigger和调度器Scheduler。
对于任务Job,实际代表的是 做什么。Quartz中操作任务时实际是操作和Job关联的JobDetail,JobDetail可以通过name和group进行唯一确定。
对于触发器Trigger,实际代表的是 什么时候做,做多久,做几次。每个Trigger也可以通过name和group进行唯一确定,且每个Trigger都会关联一个JobDetail,当Trigger定时规则满足被触发时,其关联的JobDetail就会被执行。
对于调度器Scheduler,负责JobDetail和Trigger的增删改查,以及决定在哪个实例上触发哪些Trigger来执行哪些定时任务。Quartz中真正发挥作用的调度器是QuartzScheduler,其它的各种调度器都是对QuartzScheduler的包装,真正干活儿的还是QuartzScheduler。
转载自:https://juejin.cn/post/7375345026429222923