首页  

Quartz 2.3.2 使用     所属分类 quartz 浏览量 40
Quartz v2.3.2 版本改动比较大

org.quartz-scheduler:quartz:2.3.2


quartz.properties
org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3
# 内存数据库
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore


定义任务类实现 Job 接口
package org.example.quartz.tutorial;

import org.quartz.Job;
import org.quartz.JobExecutionContext;

public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext context) {
        System.out.println("hello quartz!");
    }
}


运行 Quartz 任务调度

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzTest {

    public static void main(String[] args) {
        try {
            // 获取默认的调度器实例
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // 打开调度器
            scheduler.start();

            // 定义一个简单的任务
            JobDetail job = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("job11", "group1")
                    .build();

            // 定义一个简单的触发器: 每隔 1 秒执行 1 次,任务永不停止
            SimpleTrigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1")
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder
                            .simpleSchedule()
                            .withIntervalInSeconds(1)
                            .repeatForever()
                    ).build();

            // 开始调度任务
            scheduler.scheduleJob(job, trigger);

            // 等待任务执行一些时间
            Thread.sleep(3000);

            // 关闭调度器
            scheduler.shutdown();
        } catch (Exception se) {
            se.printStackTrace();
        }
    }
}


核心概念 触发器和作业 Schduler:调度器,主要用于管理作业(JobDetail),触发器(Trigger) JobDetail:作业实例,内部包含作业运行的具体逻辑 Trigger:触发器实例,内部包含作业执行的实践计划 Quartz 工作流程 首页基于 Job 接口定义你的作业 JobDetail 实例和触发器 Trigger 实例对象 将定义的作业和触发器实例对象通过调度器 scheduleJob,开始调度执行 调度器启动工作线程开始执行 JobDetail 实例的 execute 方法内容 任务运行时所需信息通过,JobExecutionContext 对象传递到工作线程,也可以在多个工作线程中跨线程传递 唯一标识 关于创建 JobDetail 作业实例和 Trigger 触发器的几个注意事项: 创建作业和触发器都需要通过(JobKey 和 TriggerKey + Group)组合创建唯一标识 可以通过唯一标识在 Schduler 中获取作业对象,并且管理和维护他们 引入 Group 标识的目的也是了更好的管理作业环境:例如:通过不同的 Group 来区分:【测试作业,生产作业】等 Quartz 对于 JobDetail 的处理策略: 每次执行任务都会创建一个新的 JobDetail 实例对象,意味每次执行的 JobDetail 都是新对象,JobDetail 对象也是无状态的 JobDetail 实例对象任务完成后 (execute 方法),调度器 Schduler 会将作业实例对象删除,然后进行垃圾回收 JobDetail 实例之间的状态数据,只能通过 JobExecutionContext(实际上是 JobDataMap) 进行跨作业传递 JobDataMap jobDataMap 的使用主要分 2 步: 1:在 execute() 函数内,使用 jobDataMap 获取数据 public class HelloJob implements Job { @Override public void execute(JobExecutionContext context) { // 通过 JobDataMap 对象,可以在作业的执行逻辑中,获取参数 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); String name = jobDataMap.getString("name"); System.out.println("hello " + name); } } 2:jobDataMao 添加参数 // 定义作业时,通过 usingJobData 将参数放入 JobDataMap JobDetail job = JobBuilder.newJob(HelloJob.class) .withIdentity("job11", "group1") .usingJobData("name", "phoenix") .build(); JobDataMap 使用注意事项: 虽然 JobDataMap 可以传递任意类型的数据,对象的反序列化在版本迭代中容易遇到类版本控制的问题 如果从长远的安全性考虑,尽可能的将 jobDataMap 设置为只允许存放基本类型和字符串(通过 jobStore.useProperties 设置) Quartz 会自动通过 Job 类的 setter 方法和 JobDataMap 主键匹配,自动注入属性到 Job 类中 并发性和持久化 Quartz 对于 Job 提供几个注释,合理的使用可以更好的控制 Quartz 的调度行为,具体如下: @DisallowConcurrentExecution 添加到 Job 类中,告诉 Job 防止相同定义的任务并发执行, 例如:任务 A 实例未完成任务,则任务 B 实例不会开始执行(Quartz 默认策略是不会等待,启用新线程并发调度) @PersistJobDataAfterExecution 添加到 Job 类中,默认情况下 Job 作业运行逻辑不会影响到 JobDataMap (既每个 JobDetail 拿到的都是初始化的 JobDataMap 内容), 开启该注解后,Job 的 execute() 方法完成后,对于 JobDataMap 的更新,将会被持久化到 JobDataMap 中, 从而供其他的 JobDetail 使用,这对于任务 B 依赖任务 A 的运行结果的场景下,非常有用, 强烈建议和 @DisallowConcurrentExecution 注解一起使用,会让任务运行结果更加符合预期 @DisallowConcurrentExecution @PersistJobDataAfterExecution public class QuartzTest { public static void main(String[] args) { //..... } } Trigger 触发器类型,但是他们都有几个共同的属性,如下: startTime:触发器首次生效的时间 endTime:触发器失效时间 以上共同属性的值都是 java.util.Date 对象 关于 Trigger 的其他几个概念: Priority 优先权:当调度器遇到多个同时执行的 Trigger 时候,会根据优先权大小排序,然后先后调度 Misfire 错过触发:Trigger 达到触发时间,但因为外部原因无法执行,Trigger 开始计算 Misfire 时间 常见的外部原因有哪些?例如:调度程序被关闭,线程池无可用工作线程等 Calendar 日历(不是 java.util.calendar 对象):用于排除执行日期非常有用 例如:定义一个每天 9 点执行 Trigger ,但是排除所有法定节假日 SimpleTrigger SimpleTrigger 是适用于大多数场景的触发器,它可以指定特定时间,重复间隔,重复次数等简单场景,它主要设定参数如下: 开始时间 结束时间 重复次数 间隔时间 misfire 处理策略: 从源码 SimpleScheduleBuilder 类中可以看到 MISFIRE_INSTRUCTION_SMART_POLICY 是默认的触发策略, 可以在创建 Trigger 时候设置错过触发策略 SimpleTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .withSchedule(SimpleScheduleBuilder .simpleSchedule() .withIntervalInSeconds(1) .repeatForever() // misfireInstruction = SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW; .withMisfireHandlingInstructionFireNow() ).build(); SimpleTrigger 类常量 (misfire)处理策略 MISFIRE_INSTRUCTION_FIRE_NOW MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT CronTrigger 相比 SimpleTrigger 可以指定更为复杂的执行计划,CRON 是来自 UNIX 基于时间的任务管理系统 Cron 也有类似 SimpleTrigger 的相同属性 startTime:触发器首次生效的时间 endTime:触发器失效时间 CronTrigger cronTrigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .withSchedule(CronScheduleBuilder .cronSchedule("0 0/2 8-17 * * ?") .withMisfireHandlingInstructionFireAndProceed() ) .build(); scheduler.scheduleJob(job, cronTrigger); 创建 Cron 表达式:每天上午 8 点到下午 5 点之间每隔一分钟触发一次 指定 MISFIRE_INSTRUCTION_FIRE_NOW 为 CronTrigger 的处理策略 通过 Schduler 对任务开始进行调度 CronTrigger Misfire 策略定义在 CronTrigger 常量中 Linstener 监听器 监听器用于监听 Quartz 任务事件执行对应的操作,大致分类如下: JobListener:用于监听 JobDetail 相关事件 TriggerListener:用于监听 Trigger 相关事件 SchdulerListener:用于监听 Schduler 相关事件 在常见的 JobListener 接口中,提供以下事件监听: public interface JobListener { public String getName(); // 作业即将开始执行时触发 public void jobToBeExecuted(JobExecutionContext context); // 作业即将取消时通知 public void jobExecutionVetoed(JobExecutionContext context); // 作业执行完成后通知 public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException); } 想要实现监听,需要以下几步: 自定义监听类,实现 *Listener 监听接口 在你感兴趣的事件,加入你的逻辑代码 将自定义监听类,在任务调度前,注册到 Schduler 中即可 在 Schduler 中注册一个对所有任务生效的 Listener 的示例: scheduler.getListenerManager().addJobListener(myJobListener, allJobs()); 关于使用 Listener 的建议: 在最新的 2.3.2 Listener 不会存储在 JobStore 中,所以在持久化模式下,每次启动都需要重新注册监听 大多数场景下 Quartz 用户不会使用 Listener,除非非常必要的情况才使用
JobStore 作业存储 JobStore 属性在 Quartz 配置文件中声明,用于定义 Quartz 所有运行时任务的存储方式,目前主要有两种方式 RAMJobStore 基于内存的存储模式 优点:使用,配置简单,性能最高 缺点:程序关闭后,任务信息会丢失 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore JDBCJobStore 基于数据的存储模式,其特点如下: 优点:支持常见的数据库,可以持久化保存任务信息 缺点:配置繁琐,性能不高(取决于数据库) 创建数据库表结构 Quartz 官方的 SQL DDL 脚本 quartz/tables_mysql_innodb.sql Quartz 核心表 Table Name Description QRTZ_CALENDARS 存储Quartz的Calendar信息 QRTZ_CRON_TRIGGERS 存储CronTrigger,包括Cron表达式和时区信息 QRTZ_FIRED_TRIGGERS 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息 QRTZ_PAUSED_TRIGGER_GRPS 存储已暂停的Trigger组的信息 QRTZ_SCHEDULER_STATE 存储少量的有关Scheduler的状态信息,和别的Scheduler实例 QRTZ_LOCKS 存储程序的悲观锁的信息 QRTZ_JOB_DETAILS 存储每一个已配置的Job的详细信息 QRTZ_JOB_LISTENERS 存储有关已配置的JobListener的信息 QRTZ_SIMPLE_TRIGGERS 存储简单的Trigger,包括重复次数、间隔、以及已触的次数 QRTZ_BLOG_TRIGGERS Trigger作为Blob类型存储 QRTZ_TRIGGERS 存储已配置的Trigger的信息 # quartz scheduler config org.quartz.scheduler.instanceName = MyScheduler org.quartz.threadPool.threadCount = 3 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.jobStore.dataSource = myDS # dataSource org.quartz.dataSource.myDS.driver = com.mysql.cj.jdbc.Driver org.quartz.dataSource.myDS.URL = jdbc:mysql://127.0.0.1:3306/quartz_demo org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password = test123456 org.quartz.dataSource.myDS.maxConnections = 30 在使用 JDBCJobStore 时,需要注意以下事项: Quartz 的 JobStoreTX 默认是独立示例,如果需要和其他事务一起工作(例如 J2EE 服务器),可以选择 JobStoreCMT 默认表前缀是 QRTZ_,可进行配置,使用多个不同的前缀有助于实现同一数据库的任务调度多组表结构 JDBC 委托驱动 StdJDBCDelegate 适用于大多数数据库,目前只针对测试 StdJDBCDelegate 时出现问题的类型进行特定的委托 DB2v6Delegate:适用于 DB2 版本 6 及更早版本 HSQLDBDelegate:适用于 HSQLDB 数据库 MSSQLDelegate:适用于 Microsoft SQLServer 数据库 PostgreSQLDelegate:适用于 PostgreSQL 数据库 WeblogicDelegate:由 Weblogic 制作的驱动程序 OracleDelegate:适用于 Oracle 数据库 将 org.quartz.jobStore.useProperties 设置为 True,避免将非基础类型数据存储到数据库的 BLOB 字段
springboot 集成 Quartz 整合 Springboot 非常普遍的场景,整合 Spring 可以带来好处: org.springframework.boot:spring-boot-starter-quartz public class HelloJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); String name = jobDataMap.getString("name"); System.out.println("Hello :" + name); } } 这里实现由 Springboot 提供的 QuartzJobBean,实现 executerInternal() 方法, 这是一个经过 Spring 容器包装后的任务类,可以在任务类使用 Spring 容器的实例 在控制层 Controller 提供接口,手动接收任务指定 监听 Spring 容器,在容器启动后,自动加载任务,并且注册为 Bean 手动执行 @RestController public class HelloController { @Autowired private Scheduler scheduler; @GetMapping("/hello") public void helloJob(String name) throws SchedulerException { // 定义一个的任务 JobDetail job = JobBuilder.newJob(HelloJob.class) .withIdentity("job11", "group1") .usingJobData("name", name) .build(); // 定义一个简单的触发器: 每隔 1 秒执行 1 次,任务永不停止 SimpleTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .withSchedule(SimpleScheduleBuilder .simpleSchedule() .withIntervalInSeconds(1) .repeatForever() ).build(); // 开始调度 scheduler.scheduleJob(job, trigger); } } http://localhost:8080/hello?name=phoenix @Configuration public class QuartzConfig { @Bean public JobDetail jobDetail() { JobDetail job = JobBuilder.newJob(HelloJob.class) .withIdentity("job11", "group1") .usingJobData("name", "springboot") .storeDurably() .build(); return job; } @Bean public Trigger trigger() { SimpleTrigger trigger = TriggerBuilder.newTrigger() .forJob(jobDetail()) .withIdentity("trigger1", "group1") .withSchedule(SimpleScheduleBuilder .simpleSchedule() .withIntervalInSeconds(1) .repeatForever() ).build(); return trigger; } }
集群模式 高可用,负载均衡,故障恢复 JDBC-JobStore配置群集 启用集群模式 启用 JDBCStore 或者 TerracottaJobStore 运行模式 jobStore.isClustered 属性设置为 True 每个单独实例需要设置唯一的 instanceId ( 配置集群 spring: datasource: driverClassName: com.mysql.cj.jdbc.Driver password: 123456 url: jdbc:mysql://127.0.0.1:3306/quartz_demo username: root quartz: job-store-type: jdbc properties: org: quartz: scheduler: instanceName: ClusteredScheduler # 集群名,若使用集群功能,则每一个实例都要使用相同的名字 instanceId: AUTO # 若是集群下,每个 instanceId 必须唯一,设置 AUTO 自动生成唯一 Id threadPool: class: org.quartz.simpl.SimpleThreadPool threadCount: 25 threadPriority: 5 jobStore: class: org.springframework.scheduling.quartz.LocalDataSourceJobStore driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate tablePrefix: QRTZ_ useProperties: true # 使用字符串参数,避免了将非 String 类序列化为 BLOB 的类版本问题 isClustered: true # 打开集群模式 clusterCheckinInterval: 5000 # 集群存活检测间隔 misfireThreshold: 60000 # 最大错过触发事件时间 @Configuration public class SchedulerConfig { @Autowired private DataSource dataSource; @Autowired private QuartzProperties quartzProperties; @Bean public SchedulerFactoryBean schedulerFactoryBean() { Properties properties = new Properties(); properties.putAll(quartzProperties.getProperties()); SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setOverwriteExistingJobs(true); factory.setDataSource(dataSource); factory.setQuartzProperties(properties); return factory; } } 集群模式注意事项 不要在单机模式下使用集群模式,不然会出现时钟同步问题,具体参考 NIST Internet Time Service (ITS) | NIST 不要在集群示例中,运行单机示例,不然会出现数据混乱和不稳定的情况 关于任务的运行节点是随机的(哪个节点抢到锁就可以执行) 如果不想依赖 JDBC 数据库实现集群,可以看看 TerracottaJobStore 模式

上一篇     下一篇
Linux 下使用 Java PhantomJS 和 Selenium 进行网页截图

java Selenium Chrome ChromeDriver 访问指定网页并截屏

springboot Selenium Chrome ChromeDriver 实现网页截屏

裸K price action 概述

Spring @Cacheable 注解

《底层逻辑》笔记