Article
第一章:Quartz 入门基础
1.1 Quartz 简介与核心特性
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| Quartz 定义 | 由 OpenSymphony 开源组织开发的 Java 作业调度框架,用于在指定时间或周期性执行任务。 | 需通过实现 Job 接口定义任务逻辑。 |
| 核心能力 | 支持 Cron 表达式、SimpleTrigger、任务持久化(内存/数据库)、集群部署、动态任务管理、监听器机制。 | 生产环境建议使用 JDBCJobStore 实现高可用。 |
| 架构分层 | 分为 API 层(Scheduler/Job/Trigger)、核心调度引擎、存储层(RAMJobStore / JDBCJobStore)。 | 存储层选择直接影响性能与可靠性。 |
| 调度单位 | 最小调度单位是 Job + Trigger 的组合,一个 Job 可被多个 Trigger 触发。 | JobDetail 是 Job 的”实例描述”,非 Job 本身。 |
| 并发控制 | 默认允许多个 JobDetail 实例并发执行;可通过 @DisallowConcurrentExecution 注解禁止并发。 | 该注解作用于 Job 类,对所有使用该类的 JobDetail 生效。 |
1.2 Quartz 与 Spring @Scheduled 对比
| 对比维度 | @Scheduled(Spring 原生) | Quartz |
|---|---|---|
| 依赖复杂度 | 内置于 Spring Boot,无需额外依赖,仅需 @EnableScheduling。 | 需引入 quartz 或 spring-boot-starter-quartz 依赖。 |
| 任务持久化 | 不支持,任务信息仅存在于内存中,应用重启后丢失。 | 支持 RAMJobStore(内存)和 JDBCJobStore(数据库),可持久化任务状态。 |
| 动态管理 | 仅支持静态配置,修改 cron 表达式需重启应用。 | 支持运行时动态添加、删除、暂停、恢复任务。 |
| 分布式支持 | 多实例部署时任务会重复执行,需自行加分布式锁(如 Redis)。 | 原生支持集群,通过数据库行锁保证任务仅在一个节点执行。 |
| 调度策略 | 支持 fixedRate、fixedDelay 和标准 Cron 表达式(秒级需自定义)。 | 支持 SimpleTrigger(固定间隔)、CronTrigger(完整 7 段 Cron 表达式,含秒)。 |
| 错过触发处理 | 无内置 misfire 处理机制。 | 提供多种 Misfire 策略(如 FIRE_NOW、DO_NOTHING、IGNORE_MISFIRE_POLICY)。 |
| 扩展性 | 无监听器、插件机制。 | 支持 JobListener、TriggerListener、SchedulerListener 及自定义插件。 |
| 适用场景 | 单机、简单定时任务(如缓存刷新、日志清理)。 | 企业级、复杂调度需求(如金融对账、分布式报表生成)。 |
1.3 快速搭建第一个 Quartz 应用
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 1. 添加 Maven 依赖 | 在 pom.xml 中加入依赖 | 若使用 Spring Boot,推荐使用 spring-boot-starter-quartz。 |
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.5.0</version>
</dependency>
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 2. 创建 Job 类 | 实现 org.quartz.Job 接口,重写 execute 方法 | execute 方法不能有返回值,异常需抛出 JobExecutionException。 |
public class HelloJob implements Job {
public void execute(JobExecutionContext ctx) throws JobExecutionException {
System.out.println("Hello Quartz! " + new Date());
}
}
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 3. 构建 JobDetail | 使用 JobBuilder 创建任务描述 | withIdentity 指定任务名和组名,用于唯一标识。 |
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 4. 创建 Trigger | 使用 TriggerBuilder 定义触发规则(例如每 5 秒执行) | startNow() 表示立即开始;repeatForever() 表示无限循环。 |
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5).repeatForever())
.build();
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 5. 启动 Scheduler | 获取调度器并启动 | 必须调用 start() 才会真正开始调度;scheduleJob 绑定任务与触发器。 |
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
scheduler.scheduleJob(job, trigger);
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 6.(可选)保持主线程运行 | 主方法末尾加 Thread.sleep() 防止程序退出 | 否则程序可能在任务执行前就结束。 |
Thread.sleep(60_000); // 运行 1 分钟
scheduler.shutdown();
第二章:Quartz 核心组件详解
2.1 Job 与 JobDetail
| 概念/方法名称 | 说明 | 注意事项 |
|---|---|---|
Job 接口 | 定义任务执行逻辑的接口,仅包含 execute(JobExecutionContext context) 方法。 | 必须提供无参构造函数,Quartz 通过反射创建实例。 |
JobDetail | 描述 Job 的”实例模板”,包含 Job 类、名称、组、JobDataMap 等元数据。 | 实际调度的是 JobDetail,而非 Job 实例本身。 |
JobKey | 由 name + group 构成,唯一标识一个 JobDetail。 | 注册重复 JobKey 会抛出异常,除非显式覆盖。 |
JobBuilder.newJob(Class) | 静态方法,用于构建 JobDetail,传入 Job 实现类。 | 示例:JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build() |
usingJobData(String, Object) | 向 JobDetail 的 JobDataMap 中添加键值对,供 execute 方法读取。 | 值必须可序列化(如 String、Integer),否则持久化时会失败。 |
@DisallowConcurrentExecution | 注解,标记 Job 为”有状态”(等价于旧版 StatefulJob),禁止并发执行。 | 多个 Trigger 触发同一 Job 时,会排队执行。 |
JobExecutionContext.getJobDetail() | 在 execute 方法中获取当前 JobDetail 实例。 | 可通过 .getJobDataMap() 获取传递的参数。 |
示例代码:
public class PrintJob implements Job {
public void execute(JobExecutionContext ctx) throws JobExecutionException {
String msg = ctx.getJobDetail().getJobDataMap().getString("message");
System.out.println("Message: " + msg);
}
}
JobDetail job = JobBuilder.newJob(PrintJob.class)
.withIdentity("printJob", "default")
.usingJobData("message", "Hello from Quartz!")
.build();
2.2 Trigger(SimpleTrigger 与 CronTrigger)
| 方法/属性名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
TriggerBuilder.newTrigger() | 静态工厂方法 | 创建 Trigger 构建器 | TriggerBuilder.newTrigger().withIdentity("t1", "g1") | 所有 Trigger 均由此构建 |
startNow() | .startNow() | 立即开始触发 | .startNow() | 调度器 start() 后立即生效 |
startAt(Date) | .startAt(myDate) | 指定首次触发时间 | .startAt(new Date(System.currentTimeMillis() + 60_000)) | 时间点必须在将来 |
endAt(Date) | .endAt(endTime) | 设置 Trigger 失效时间 | .endAt(dateOf(23, 0, 0)) | 到达后不再触发,即使未达重复次数 |
withSchedule(SimpleScheduleBuilder) | .withSchedule(simpleSchedule().withIntervalInSeconds(10).repeatForever()) | 定义 SimpleTrigger 调度规则 | 见左列 | 适用于固定间隔任务 |
withSchedule(CronScheduleBuilder) | .withSchedule(cronSchedule("0 0 9 * * ?")) | 定义 CronTrigger 调度规则 | .withSchedule(cronSchedule("0/5 * * * * ?")) | 支持 6 或 7 段 Cron 表达式(含秒) |
withPriority(int) | .withPriority(10) | 设置 Trigger 优先级(默认 5) | .withPriority(8) | 仅在资源不足时影响调度顺序 |
usingJobData(String, Object) | .usingJobData("key", "value") | 向 Trigger 的 JobDataMap 添加参数 | .usingJobData("triggerSource", "manual") | 与 JobDetail 的 JobDataMap 合并传入 execute |
SimpleTrigger 示例(每 3 秒执行一次,共 5 次):
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("simpleTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.withRepeatCount(4)) // 总共执行 5 次(含首次)
.build();
CronTrigger 示例(每天上午 9:30 执行):
Trigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity("dailyTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 30 9 * * ?"))
.build();
注意事项:
- Cron 表达式格式:
[秒] [分] [时] [日] [月] [周] [年(可选)] - SimpleTrigger 的
repeatCount=0表示只执行一次;REPEAT_INDEFINITELY表示无限循环。 - Trigger 的 JobDataMap 与 JobDetail 的 JobDataMap 会合并,若 key 冲突,Trigger 的值优先。
2.3 Scheduler 调度器生命周期管理
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
StdSchedulerFactory.getDefaultScheduler() | Scheduler sched = StdSchedulerFactory.getDefaultScheduler(); | 获取默认 Scheduler 实例 | 见左列 | 使用 quartz.properties 配置 |
scheduler.start() | scheduler.start(); | 启动调度器,开始执行任务 | scheduler.start(); | 必须调用,否则任务不运行 |
scheduler.standby() | scheduler.standby(); | 暂停所有任务调度(可恢复) | scheduler.standby(); | 不释放线程池,仅暂停触发 |
scheduler.resumeAll() | scheduler.resumeAll(); | 恢复 standby 状态 | scheduler.resumeAll(); | 仅对 standby 有效 |
scheduler.shutdown() | scheduler.shutdown(); | 关闭调度器,停止所有任务 | scheduler.shutdown(); | 默认等待任务完成(可传 true 强制立即关闭) |
scheduler.scheduleJob(JobDetail, Trigger) | scheduler.scheduleJob(job, trigger); | 注册任务与触发器 | 见左列 | 一个 JobDetail 可绑定多个 Trigger |
scheduler.unscheduleJob(TriggerKey) | scheduler.unscheduleJob(triggerKey); | 删除指定 Trigger | scheduler.unscheduleJob(TriggerKey.triggerKey("t1", "g1")); | 对应 JobDetail 若无其他 Trigger 则变为”孤立” |
scheduler.deleteJob(JobKey) | scheduler.deleteJob(jobKey); | 删除 JobDetail 及其所有关联 Trigger | scheduler.deleteJob(JobKey.jobKey("job1", "g1")); | 彻底移除任务 |
scheduler.getContext() | SchedulerContext ctx = scheduler.getContext(); | 获取调度器上下文(全局共享 Map) | ctx.put("globalConfig", config); | 所有 Job/Trigger 可通过 JobExecutionContext 访问 |
生命周期流程:
- 通过 SchedulerFactory 创建 Scheduler;
- 调用
start()进入运行状态; - 可随时
standby()暂停、resumeAll()恢复; - 最终调用
shutdown()结束生命周期。
注意事项:
- Scheduler 是线程安全的,可在多线程环境中共享使用。
- 调用
shutdown(true)会中断正在执行的 Job 线程,需谨慎使用。 - 在 Spring Boot 中,Scheduler 通常由容器管理,无需手动 shutdown。
第三章:Quartz 高级功能
3.1 JobDataMap 数据传递机制
| 方法/机制名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
JobDetail.usingJobData(String, Object) | JobBuilder.newJob(...).usingJobData("name", "Alice") | 向 JobDetail 的 JobDataMap 添加参数 | 见左列 | 值必须可序列化(如 String、Integer、Float) |
Trigger.usingJobData(String, Object) | TriggerBuilder.newTrigger().usingJobData("source", "API") | 向 Trigger 的 JobDataMap 添加参数 | 见左列 | 用于区分不同 Trigger 触发同一 Job 的上下文 |
JobExecutionContext.getJobDetail().getJobDataMap() | context.getJobDetail().getJobDataMap().getString("name") | 获取 JobDetail 中的参数 | 见左列 | 仅读取 JobDetail 定义的数据 |
JobExecutionContext.getTrigger().getJobDataMap() | context.getTrigger().getJobDataMap().getString("source") | 获取 Trigger 中的参数 | 见左列 | 仅读取当前 Trigger 定义的数据 |
JobExecutionContext.getMergedJobDataMap() | context.getMergedJobDataMap().getString("key") | 获取合并后的 JobDataMap(Trigger 优先) | 见左列 | 若 key 冲突,Trigger 的值覆盖 JobDetail 的值 |
| 自动注入(Setter 注入) | Job 类中定义 setName(String),Quartz 自动调用 | 简化参数获取,无需手动 getXXX | 见下方示例 | JobFactory 必须支持(默认 StdJobFactory 支持);字段名需与 key 一致 |
put() / get() 方法 | dataMap.put("count", 1); int c = dataMap.getInt("count"); | 存取任意类型数据(需强转) | 见左列 | 推荐使用类型安全的 getXXX() 方法(如 getString、getInt) |
自动注入示例:
public class MyJob implements Job {
private String name;
public void setName(String name) { this.name = name; }
// ...
}
注意事项:
- JobDataMap 实现了
java.util.Map<String, Object>,但建议使用其提供的类型安全方法。 - 在 JDBCJobStore 模式下,所有存入 JobDataMap 的对象必须实现
Serializable,否则启动时报错。 - 修改 JobDataMap 中的值(如计数器)在 RAMJobStore 中有效,但在 JDBCJobStore 中默认不会持久化回数据库,除非显式启用
@PersistJobDataAfterExecution(已废弃)或使用有状态 Job + 手动更新。
3.2 监听器(JobListener / TriggerListener / SchedulerListener)
JobListener
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
getName() | public String getName() { return "MyJobListener"; } | 返回监听器唯一名称 | 见左列 | 非全局监听器注册时需匹配此名称 |
jobToBeExecuted(JobExecutionContext) | public void jobToBeExecuted(ctx) { ... } | Job 即将执行前回调 | 记录开始时间、前置校验 | 可抛异常中断任务(但不推荐) |
jobExecutionVetoed(JobExecutionContext) | public void jobExecutionVetoed(ctx) { ... } | Job 被 TriggerListener 否决时回调 | 记录被拒绝原因 | 仅当 TriggerListener.vetoJobExecution() 返回 true 时触发 |
jobWasExecuted(JobExecutionContext, JobExecutionException) | public void jobWasExecuted(ctx, e) { ... } | Job 执行完成后回调(无论成功失败) | 记录耗时、结果、异常处理 | 可在此处重试或告警 |
注册方式:
// 全局监听器(监听所有 Job)
scheduler.getListenerManager().addJobListener(new MyJobListener());
// 非全局监听器(仅监听特定 Job)
scheduler.getListenerManager().addJobListener(new MyJobListener(),
KeyMatcher.jobKeyEquals(JobKey.jobKey("myJob", "group1")));
TriggerListener
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
getName() | return "MyTriggerListener"; | 返回监听器名称 | 见左列 | 非全局注册时需匹配 |
triggerFired(Trigger, JobExecutionContext) | public void triggerFired(t, ctx) { ... } | Trigger 触发后、Job 执行前回调 | 记录触发时间 | 可配合 veto 使用 |
vetoJobExecution(Trigger, JobExecutionContext) | public boolean vetoJobExecution(t, ctx) { return true; } | 决定是否否决本次 Job 执行 | 条件判断(如资源不足) | 返回 true 则 Job 不执行,触发 jobExecutionVetoed |
triggerMisfired(Trigger) | public void triggerMisfired(t) { ... } | Trigger 错过触发时回调 | 记录 misfire、补偿逻辑 | 避免耗时操作,防止雪崩 |
triggerComplete(Trigger, JobExecutionContext, CompletedExecutionInstruction) | public void triggerComplete(t, ctx, inst) { ... } | Trigger 完成一次调度后回调 | 清理资源、统计 | inst 表示后续行为(如是否重新调度) |
注册方式:
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener());
// 或绑定到特定 Trigger
scheduler.getListenerManager().addTriggerListener(listener, nameEquals("myTrigger"));
SchedulerListener
| 方法名称 | 用途 | 注意事项 |
|---|---|---|
jobScheduled(Trigger) | 新 Job 被调度时调用 | 可用于监控任务注册 |
jobUnscheduled(TriggerKey) | Job 被取消调度时调用 | |
triggerFinalized(Trigger) | Trigger 永久失效时调用(如 SimpleTrigger 达到 repeatCount) | |
triggerPaused(TriggerKey) / triggersPaused(String group) | Trigger 或组被暂停 | |
triggerResumed(TriggerKey) / triggersResumed(String group) | Trigger 或组恢复 | |
jobAdded(JobDetail) | JobDetail 被添加到 Scheduler | |
jobDeleted(JobKey) | Job 被删除 | |
schedulerError(String msg, SchedulerException cause) | 调度器发生严重错误 | 重要!可用于告警 |
schedulerShutdown() | 调度器关闭时调用 | 可用于清理资源 |
注册方式:
scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());
3.3 插件(Plugins)与日志记录
| 插件名称 | 配置方式(quartz.properties) | 用途 | 注意事项 |
|---|---|---|---|
| ShutdownHookPlugin | org.quartz.plugin.shutdownHook.class = org.quartz.plugins.management.ShutdownHookPlugin | JVM 关闭时自动 shutdown Scheduler | 避免任务中断,确保优雅退出 |
org.quartz.plugin.shutdownHook.cleanShutdown = true | |||
| LoggingJobHistoryPlugin | org.quartz.plugin.jobHistory.class = org.quartz.plugins.history.LoggingJobHistoryPlugin | 通过 SLF4J 记录 Job 执行历史(开始/结束) | 默认 INFO 级别,可调整日志级别控制输出 |
| LoggingTriggerHistoryPlugin | org.quartz.plugin.triggerHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin | 记录 Trigger 触发历史(fire/misfire/complete) | 便于排查调度问题 |
| 自定义插件 | 实现 org.quartz.spi.SchedulerPlugin 接口 | 扩展初始化/销毁逻辑(如连接外部系统) | initialize() 在 Scheduler 初始化后调用 |
| 在 properties 中声明 |
日志记录说明:
- Quartz 使用 SLF4J 作为日志门面,需引入具体实现(如 logback、log4j2)。
- 核心包日志类别:
org.quartz.core(调度线程)、org.quartz.impl(工厂)、org.quartz.simpl(简单实现)。 - 开启 DEBUG 日志可查看详细调度过程(如 Trigger 匹配、线程分配)。
示例:启用历史日志插件(quartz.properties):
org.quartz.plugin.jobHistory.class = org.quartz.plugins.history.LoggingJobHistoryPlugin
org.quartz.plugin.jobHistory.jobSuccessMessage = Job [{1}.{0}] finished at {2} with result: {3}
org.quartz.plugin.jobHistory.jobFailedMessage = Job [{1}.{0}] failed at {2}
注意事项:
- 插件在 Scheduler 初始化阶段加载,顺序由配置决定。
- 生产环境建议启用 ShutdownHookPlugin 防止任务丢失。
- 日志插件可能产生大量日志,需合理设置日志级别和滚动策略。
第四章:Quartz 配置与持久化
4.1 quartz.properties 配置详解
| 配置项前缀 | 属性名 | 默认值 | 用途 | 注意事项 |
|---|---|---|---|---|
org.quartz.scheduler | instanceName | QuartzScheduler | 调度器实例名称,在集群中需全局一致 | 用于区分多个调度器实例 |
instanceId | NON_CLUSTERED | 实例唯一 ID;设为 AUTO 可自动生成 | 集群模式下必须唯一,建议设为 AUTO | |
threadName | instanceName + "_QuartzSchedulerThread" | 主调度线程名称 | 便于线程监控 | |
idleWaitTime | 30000(毫秒) | 空闲时轮询触发器的等待时间 | 不建议低于 5000ms,避免频繁 DB 查询 | |
dbFailureRetryInterval | 15000(毫秒) | 数据库连接失败后的重试间隔 | 仅在使用 JDBCJobStore 时生效 | |
org.quartz.threadPool | class | 无(必须显式设置) | 线程池实现类 | 通常设为 org.quartz.simpl.SimpleThreadPool |
threadCount | 无(必须显式设置) | 工作线程数量 | 至少为 1;根据任务并发量调整,一般 10~50 | |
threadPriority | 5(NORMAL_PRIORITY) | 线程优先级(1~10) | 不建议过高或过低 | |
threadsInheritContextClassLoaderOfInitializingThread | true | 子线程是否继承初始化线程的 ClassLoader | 在应用服务器中建议设为 true | |
org.quartz.jobStore | class | org.quartz.simpl.RAMJobStore | 作业存储实现类 | 持久化需改为 org.quartz.impl.jdbcjobstore.JobStoreTX |
misfireThreshold | 60000(毫秒) | 触发器错过执行的最大容忍时间 | 超过则视为 misfire,进入 misfire 处理流程 | |
tablePrefix | QRTZ_ | 数据库表前缀 | 需与建表脚本一致 | |
useProperties | false | JobDataMap 是否仅存字符串类型 | 设为 true 可提升序列化兼容性 | |
driverDelegateClass | 无(自动推断) | 数据库方言代理类 | MySQL 建议显式设为 StdJDBCDelegate | |
isClustered | false | 是否启用集群模式 | 集群部署时必须设为 true | |
clusterCheckinInterval | 15000(毫秒) | 节点心跳上报间隔 | 控制故障检测灵敏度 | |
org.quartz.dataSource.[name] | driver | 无 | JDBC 驱动类名 | 如 com.mysql.cj.jdbc.Driver |
URL | 无 | 数据库连接 URL | 如 jdbc:mysql://localhost:3306/quartz?useSSL=false | |
user / password | 无 | 数据库账号密码 | ||
maxConnections | 无 | 最大连接数 | 建议 ≥ threadCount |
典型配置示例(JDBCJobStore + MySQL):
org.quartz.scheduler.instanceName = MyScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 20
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
org.quartz.dataSource.myDS.driver = com.mysql.cj.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz?useSSL=false&serverTimezone=UTC
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password = password
org.quartz.dataSource.myDS.maxConnections = 25
4.2 RAMJobStore 与 JDBCJobStore 对比
| 对比维度 | RAMJobStore | JDBCJobStore |
|---|---|---|
| 存储位置 | JVM 内存 | 关系型数据库(如 MySQL、Oracle) |
| 性能 | 极高(内存访问) | 较低(依赖数据库 I/O) |
| 持久性 | 应用重启后所有任务丢失 | 任务信息持久化,重启后自动恢复 |
| 集群支持 | 不支持 | 原生支持,通过数据库行锁保证单点执行 |
| 事务控制 | 无 | 支持本地事务(JobStoreTX)或 JTA(JobStoreCMT) |
| 适用场景 | 单机、测试、临时任务 | 生产环境、高可用、动态任务管理 |
| 配置复杂度 | 极简(默认即用) | 需配置数据源、建表、调优 |
| 资源消耗 | 仅占用堆内存 | 需维护数据库连接、表结构 |
| 任务状态恢复 | 无法恢复 | 自动从 QRTZ_JOB_DETAILS、QRTZ_TRIGGERS 等表加载 |
| 典型配置 | org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore | org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX |
注意事项:
- RAMJobStore 适合开发调试,但绝不应用于生产环境。
- JDBCJobStore 需预先创建 Quartz 官方提供的 11 张表(如
QRTZ_JOB_DETAILS、QRTZ_TRIGGERS等)。 - 在集群模式下,所有节点必须使用相同的
instanceName和不同的instanceId,并共享同一数据库。
4.3 使用数据库实现任务持久化
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 1. 添加依赖 | 在 Maven 中加入 spring-boot-starter-quartz 以及数据库驱动(如 mysql-connector-java) | 若非 Spring Boot 项目,需手动引入 quartz 和 HikariCP 等 |
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 2. 创建数据库与表 | 执行 Quartz 官方 SQL 脚本(位于 quartz-x.x.jar/org/quartz/impl/jdbcjobstore/) | 首次运行可设 initialize-schema: always,后续改为 never 防止表重建 |
- MySQL:
tables_mysql_innodb.sql - Oracle:
tables_oracle.sql
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 3. 配置 application.yml | 见下方配置示例 | initialize-schema: always 仅首次使用,否则会清空任务表 |
spring:
quartz:
job-store-type: jdbc
jdbc:
initialize-schema: never # 第二次启动后必须为 never
properties:
org.quartz.scheduler.instanceName: QuartzScheduler
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.threadPool.threadCount: 10
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 4. 创建持久化 Job | 见下方代码示例 | .storeDurably() 是关键,确保 JobDetail 存入数据库 |
JobDetail job = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "group1")
.storeDurably() // 必须!否则重启后丢失
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(job)
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
.build();
scheduler.scheduleJob(job, trigger);
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 5. 启用有状态 Job(可选) | 见下方代码示例 | 用于需要保存 JobDataMap 修改状态的场景(如计数器) |
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class StatefulJob implements Job { ... }
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 6. 验证持久化 | - 启动应用,确认任务执行 - 查看 QRTZ_JOB_DETAILS 表是否有记录- 停止应用后重启,观察任务是否自动恢复 | 若未恢复,检查 storeDurably() 是否调用、initialize-schema 是否误设为 always |
关键点总结:
.storeDurably():必须调用,否则 Job 被视为”临时”,不写入数据库。initialize-schema: never:第二次及以后启动必须设置,防止 Quartz 重建表导致任务丢失。- 数据库表结构:共 11 张表,核心包括
QRTZ_JOB_DETAILS(任务定义)、QRTZ_TRIGGERS(触发规则)、QRTZ_FIRED_TRIGGERS(正在执行的任务)等。
第五章:Quartz 集群与分布式调度
5.1 集群原理与配置要点
| 概念/配置项 | 说明 | 注意事项 |
|---|---|---|
| 无中心架构 | Quartz 集群中所有节点地位平等,无主从之分,通过共享数据库协调任务执行。 | 节点间不直接通信,仅通过数据库表感知彼此状态。 |
| 数据库行锁机制 | 调度线程通过 SELECT ... FOR UPDATE 获取 QRTZ_LOCKS 表中的 TRIGGER_ACCESS 锁,确保同一时间仅一个节点扫描并获取待触发的 Trigger。 | 数据库必须支持行级锁(如 InnoDB),MyISAM 不适用。 |
| 心跳检测 | 每个节点定期更新 QRTZ_SCHEDULER_STATE 表中的 last_checkin_time,其他节点据此判断是否宕机。 | clusterCheckinInterval 控制心跳频率(默认 15 秒)。 |
| 故障转移 | 当某节点宕机,其正在执行的任务(记录在 QRTZ_FIRED_TRIGGERS)会被其他节点检测到,并根据 REQUESTS_RECOVERY=true 标记决定是否重新执行。 | Job 必须设置 requestsRecovery = true 才能被恢复。 |
| 关键配置项 | 见下方代码示例 | 所有节点必须使用相同的 instanceName 和不同的 instanceId(推荐 AUTO)。 |
| 时钟同步要求 | 所有集群节点系统时间必须基本一致(误差 < misfireThreshold)。 | 时间不同步会导致任务漏触发或重复触发。 |
| 线程池独立性 | 每个节点拥有独立的线程池,任务在其本地执行。 | 总并发能力 = 节点数 × 单节点 threadCount。 |
典型 quartz.properties 集群配置片段:
org.quartz.scheduler.instanceName = ClusteredScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.threadPool.threadCount = 15
5.2 分布式任务去重与高可用
| 机制名称 | 实现方式 | 用途 | 注意事项 |
|---|---|---|---|
| 数据库行锁(核心) | 调度线程在获取待执行 Trigger 前,先锁定 QRTZ_LOCKS 表的 TRIGGER_ACCESS 行。 | 确保同一 Trigger 仅被一个节点获取并执行。 | 依赖数据库事务和行锁,是 Quartz 去重的根本保障。 |
| Trigger 状态流转 | Trigger 状态从 WAITING → ACQUIRED → EXECUTING → COMPLETE,状态变更由持有锁的节点完成。 | 防止多个节点同时认为某个 Trigger 可执行。 | 状态字段位于 QRTZ_TRIGGERS.trigger_state。 |
| Job 恢复标记 | 在 JobDetail 中设置 .requestRecovery(true),当执行节点宕机,其他节点会重新执行该 Job。 | 实现任务高可用,避免因节点故障丢失任务。 | 仅对设置了 requestsRecovery=true 的 Job 生效;需 Job 本身幂等。 |
| Misfire 处理 | 若 Trigger 触发时间已过且超过 misfireThreshold,进入 misfire 流程,按策略处理(如立即执行、忽略)。 | 应对节点长时间离线后重启的场景。 | 可通过 withMisfireHandlingInstructionXXX() 自定义策略。 |
| 负载均衡 | 多个节点竞争获取 Trigger,自然实现任务分发。 | 避免单点过载,提升整体吞吐量。 | 任务分配不保证绝对均匀,取决于调度时机。 |
| 避免重复注册 | 动态添加任务时,应先检查 QRTZ_JOB_DETAILS 是否已存在同名 Job。 | 防止多节点同时初始化导致重复任务。 | 可通过 scheduler.checkExists(JobKey) 判断。 |
示例:创建可恢复的 Job:
JobDetail job = JobBuilder.newJob(RecoverableJob.class)
.withIdentity("recoverJob", "group1")
.requestRecovery(true) // 关键!启用故障恢复
.storeDurably()
.build();
// Job 实现需幂等
public class RecoverableJob implements Job {
public void execute(JobExecutionContext ctx) throws JobExecutionException {
// 例如:处理消息队列中的消息,需支持重复消费
}
}
注意事项:
- 幂等性:由于任务可能被重复执行(如恢复场景),Job 逻辑必须设计为幂等。
- 网络分区:若数据库不可达,所有节点将停止调度,直至恢复。
- 性能瓶颈:数据库成为集群性能瓶颈,需优化 DB 连接池、索引(如
QRTZ_TRIGGERS表的next_fire_time索引)。
5.3 Terracotta 与 Quartz 集群集成(可选)
| 项目 | 说明 | 注意事项 |
|---|---|---|
| Terracotta 是什么 | 一种 JVM 级别的分布式内存网格(Distributed Shared Memory),可替代数据库实现无 DB 集群。 | 由 Software AG 提供,商业产品(开源版已停止维护)。 |
| Quartz-Terracotta 集成原理 | 使用 TerracottaJobStore 将 Job/Trigger 状态存储在 Terracotta 服务器的共享内存中,节点通过 Terracotta 客户端通信。 | 无需数据库,减少 I/O 开销,理论性能更高。 |
| 配置方式 | org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStoreorg.quartz.jobStore.tcConfigUrl = localhost:9510 | 需部署 Terracotta Server 并启动。 |
| 优势 | - 无数据库依赖 - 更低延迟 - 支持复杂对象存储(无需序列化限制) | |
| 劣势 | - 引入额外中间件(Terracotta Server) - 社区支持弱 - 与主流云原生架构不兼容 | 官方已不推荐新项目使用 |
| 现状 | 自 Quartz 2.0 起,Terracotta 集成由第三方维护;主流生产环境普遍采用 JDBCJobStore + MySQL/PostgreSQL。 | 不建议在新项目中采用,除非有特殊历史原因。 |
替代方案建议:
- 对于追求高性能、无 DB 的场景,可考虑 Elastic-Job、XXL-JOB 或 ShedLock + Spring
@Scheduled。 - 对于云原生环境,推荐 Kubernetes CronJob + 幂等任务或 Quartz + RDBMS(成熟稳定)。
第六章:Quartz 与 Spring Boot 整合
6.1 spring-boot-starter-quartz 使用方式
| 配置/组件 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| Maven 依赖 | org.springframework.boot:spring-boot-starter-quartz | 自动引入 Quartz 核心库及 Spring 集成支持 | 见下方示例 | 无需单独引入 quartz,版本由 Spring Boot 管理 |
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
| 配置/组件 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 自动配置类 | QuartzAutoConfiguration(通过 spring.factories 加载) | 自动创建 SchedulerFactoryBean、Scheduler Bean | 无须手动配置 | 可通过 @EnableAutoConfiguration(exclude = QuartzAutoConfiguration.class) 禁用 |
| 注入 Scheduler | @Autowired private Scheduler scheduler; | 获取已初始化的调度器实例 | 见下方示例 | Scheduler 已启动,可直接使用 |
@Service
public class JobService {
@Autowired
private Scheduler scheduler;
}
| 配置/组件 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| Job 支持 Spring Bean 注入 | Job 类加 @Component,字段用 @Autowired | 在 Job 中使用 Service、Repository 等 Spring Bean | 见下方示例 | 依赖 AutowiringSpringBeanJobFactory(Starter 默认启用) |
@Component
public class MyJob implements Job {
@Autowired
private UserService userService;
public void execute(...) { ... }
}
| 配置/组件 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
application.yml 配置 | spring.quartz.* 命名空间 | 覆盖默认 Quartz 属性(如线程池、存储类型) | 见下方示例 | 所有 org.quartz.* 属性均可通过 properties 子项设置 |
spring:
quartz:
job-store-type: jdbc
properties:
org.quartz.threadPool.threadCount: 20
| 配置/组件 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 禁用自动启动 | spring.quartz.auto-startup=false | 控制 Scheduler 是否在应用启动时自动 start() | spring.quartz.auto-startup: false | 适用于需要延迟启动或条件启动的场景 |
关键机制说明:
- SchedulerFactoryBean:Spring 封装的 FactoryBean,负责创建和管理 Scheduler 生命周期。
- AdaptableJobFactory:将 Quartz Job 实例交由 Spring 容器管理,实现依赖注入。
- 配置映射:
spring.quartz.properties.org.quartz.xxx→ 最终写入quartz.properties。
6.2 动态添加/删除任务
| 操作类型 | 方法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 动态添加 Cron 任务 | scheduler.scheduleJob(JobDetail, Trigger) | 运行时注册新任务 | 见下方完整示例 | Job 必须 storeDurably() 才能持久化 |
| 更新任务 Cron 表达式 | scheduler.rescheduleJob(TriggerKey, newTrigger) | 修改已有任务的调度时间 | scheduler.rescheduleJob(TriggerKey.triggerKey(oldName, group), newTrigger); | 推荐使用 rescheduleJob() 而非 delete+add,保留任务状态 |
| 暂停任务 | scheduler.pauseJob(JobKey) | 暂停指定 Job 的所有 Trigger | scheduler.pauseJob(JobKey.jobKey("myJob", "group1")); | 任务状态变为 PAUSED,不触发但保留定义 |
| 恢复任务 | scheduler.resumeJob(JobKey) | 恢复已暂停的任务 | scheduler.resumeJob(JobKey.jobKey("myJob", "group1")); | 从暂停点继续调度 |
| 删除任务 | scheduler.deleteJob(JobKey) | 彻底移除 Job 及其所有 Trigger | scheduler.deleteJob(JobKey.jobKey("myJob", "group1")); | 数据库中对应记录被清除 |
| 检查任务是否存在 | scheduler.checkExists(JobKey) | 判断 Job 是否已注册 | boolean exists = scheduler.checkExists(JobKey.jobKey("myJob", "group1")); | 避免重复添加 |
完整服务类示例:
@Service
public class QuartzDynamicService {
@Autowired
private Scheduler scheduler;
public void addCronJob(String jobName, String group,
Class<? extends Job> jobClass, String cron)
throws SchedulerException {
JobDetail job = JobBuilder.newJob(jobClass)
.withIdentity(jobName, group)
.storeDurably()
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(job)
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
scheduler.scheduleJob(job, trigger);
}
public void updateCronJob(String jobName, String group, String newCron)
throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", group);
CronTrigger newTrigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(newCron))
.build();
scheduler.rescheduleJob(triggerKey, newTrigger);
}
public void deleteJob(String jobName, String group)
throws SchedulerException {
scheduler.deleteJob(JobKey.jobKey(jobName, group));
}
}
注意事项:
- 动态任务若需持久化,必须使用 JDBCJobStore 并调用
.storeDurably()。 rescheduleJob()会保留原 Trigger 的状态(如 misfire 记录),优于 delete+add。- 所有操作均为线程安全,可在 Web 请求中直接调用。
6.3 Web 管理界面集成(如 Quartz Web UI)
| 方案 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 自研 REST API + 前端 | 提供 /jobs/list、/jobs/pause、/jobs/resume 等接口,前端调用 | 完全可控,可深度定制 | 开发成本高 | 企业内部系统,需与现有平台集成 |
| quartz-manager(第三方) | 引入开源项目 | 快速获得可视化界面 | 功能有限,维护状态不明 | 快速原型验证 |
| 整合 XXL-JOB / ElasticJob | 使用更高级的调度框架替代 Quartz | 提供成熟 Web UI、权限控制、执行日志 | 需迁移现有任务 | 新项目或重构场景 |
| Spring Boot Admin 扩展 | 通过 Actuator Endpoint 暴露任务信息,Admin 展示 | 与 Spring 生态无缝集成 | 仅支持查看,不支持操作 | 监控为主,操作通过 API |
| Swagger + Controller | 用 Swagger 文档化动态任务 API | 开发者友好,无需额外 UI | 无图形化操作界面 | 内部调试或轻量级管理 |
自研 Web API 示例(Controller):
@RestController
@RequestMapping("/quartz")
public class QuartzController {
@Autowired
private QuartzDynamicService quartzService;
@PostMapping("/job/add")
public ResponseEntity<String> addJob(@RequestParam String name,
@RequestParam String group,
@RequestParam String cron) {
try {
quartzService.addCronJob(name, group, MyJob.class, cron);
return ResponseEntity.ok("Added");
} catch (Exception e) {
return ResponseEntity.status(500).body(e.getMessage());
}
}
@PostMapping("/job/delete")
public ResponseEntity<String> deleteJob(@RequestParam String name,
@RequestParam String group) {
try {
quartzService.deleteJob(name, group);
return ResponseEntity.ok("Deleted");
} catch (Exception e) {
return ResponseEntity.status(500).body(e.getMessage());
}
}
}
推荐实践:
- 对于简单需求,自研 REST API + Swagger 是最轻量高效的方式。
- 若需生产级 UI,建议评估 XXL-JOB(国产、中文文档、活跃社区)而非强行扩展 Quartz。
- 不要直接暴露 Scheduler 操作给前端,应封装为业务语义接口(如”开启每日报表”而非”addJob”)。
第七章:最佳实践与常见问题
7.1 任务幂等性与异常处理
| 机制/策略 | 实现方式 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 业务唯一标识(Idempotency Key) | 在 JobDataMap 中传入唯一业务 ID(如订单号),执行前查 DB 是否已处理 | 防止重复创建/扣款 | 见下方示例 | 唯一 ID 需由调用方生成并传递 |
String orderId = ctx.getMergedJobDataMap().getString("orderId");
if (orderService.exists(orderId))
return; // 已处理,直接返回
| 机制/策略 | 实现方式 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 数据库唯一索引 | 在业务表上建立 (job_type, biz_id) 联合唯一索引 | 利用 DB 层阻止重复插入 | 见下方示例 | 捕获 DuplicateKeyException 并静默处理 |
CREATE UNIQUE INDEX uk_job_order ON job_execution_log(job_type, order_id);
| 机制/策略 | 实现方式 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 状态机校验 | 执行前检查业务对象当前状态是否允许操作 | 避免非法状态变更 | 见下方示例 | 适用于订单、支付等有明确状态流转的场景 |
Order order = orderDao.findById(orderId);
if (order.getStatus() != OrderStatus.CREATED)
return; // 已支付,不重复处理
| 机制/策略 | 实现方式 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 分布式锁(Redis) | 使用 Redisson 或 Lua 脚本加锁,确保同一任务仅一个实例执行 | 防止集群下并发执行 | 见下方示例 | 锁粒度要细(如按订单 ID),避免全局锁 |
RLock lock = redisson.getLock("job:lock:" + jobId);
if (lock.tryLock(0, 30, SECONDS)) { ... }
| 机制/策略 | 实现方式 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| Job 异常处理 | 在 execute 方法中捕获异常,记录日志,决定是否抛出 JobExecutionException | 控制重试行为 | 见下方示例 | JobExecutionException 的 refireImmediately 参数控制是否重试 |
try { ... } catch (Exception e) {
log.error("Job failed", e);
throw new JobExecutionException(e, false); // false=不立即重试
}
| 机制/策略 | 实现方式 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 启用 Misfire 处理 | Trigger 构建时指定 misfire 策略 | 应对错过触发的情况 | 见下方示例 | 默认策略可能不符合业务预期,需显式设置 |
CronScheduleBuilder.cronSchedule(cron)
.withMisfireHandlingInstructionDoNothing();
幂等性设计原则:
- 核心思想:无论任务执行 1 次还是 N 次,最终业务状态一致。
- 优先顺序:业务状态校验 > 唯一索引 > 分布式锁(成本递增)。
- Quartz 本身不保证幂等,必须在 Job 逻辑中实现。
7.2 性能调优(线程池、数据库连接等)
| 调优维度 | 配置项/参数 | 推荐值 | 说明 | 注意事项 |
|---|---|---|---|---|
| 线程池大小 | org.quartz.threadPool.threadCount | CPU 核数 × (1 + 平均等待时间/平均计算时间) | 控制并发任务数 | 过小导致任务堆积,过大引发上下文切换开销;一般 10~100 |
| 数据库连接池 | HikariCP maximumPoolSize | ≥ threadCount + 5 | 确保每个工作线程+调度线程有足够连接 | Quartz 调度线程也需 DB 连接(用于扫描 Trigger) |
| Trigger 扫描间隔 | org.quartz.scheduler.idleWaitTime | 10000~30000 ms | 调度线程空闲时休眠时间 | 过小增加 DB 查询频率,过大延迟任务触发 |
| Misfire 容忍阈值 | org.quartz.jobStore.misfireThreshold | 60000 ms(默认) | 触发器错过执行的最大容忍时间 | 根据业务容忍度调整,过小导致频繁 misfire |
| 批量获取 Trigger | org.quartz.jobStore.maxTriggersToAcquire | 1~5(默认 1) | 单次从 DB 获取的待触发 Trigger 数量 | 增大可减少 DB 查询次数,但可能影响负载均衡 |
| 数据库索引优化 | QRTZ_TRIGGERS 表索引 | (next_fire_time, state) | 加速调度线程查询待触发任务 | 必须存在,否则高并发下 DB CPU 飙升 |
| 禁用未使用功能 | 移除监听器、插件 | — | 减少回调开销 | 如无需历史日志,禁用 LoggingJobHistoryPlugin |
典型调优配置(application.yml):
spring:
quartz:
properties:
org.quartz.threadPool.threadCount: 30
org.quartz.scheduler.idleWaitTime: 15000
org.quartz.jobStore.misfireThreshold: 120000
datasource:
hikari:
maximum-pool-size: 40
minimum-idle: 10
监控指标:
- 线程池活跃线程数:接近
threadCount表示任务堆积。 - DB 查询 QPS:
SELECT * FROM QRTZ_TRIGGERS WHERE next_fire_time < ?频率过高需调大idleWaitTime。 - 任务执行延迟:记录
triggerFired与jobToBeExecuted时间差。
7.3 常见错误排查(如任务不触发、重复执行等)
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 任务完全不触发 | 1. Scheduler 未 start()2. Job 未调用 .storeDurably()(持久化模式)3. Cron 表达式错误 | 1. 检查是否调用 scheduler.start()2. 查看 QRTZ_JOB_DETAILS 表是否有记录3. 用在线 Cron 工具验证表达式 | 1. 确保启动 Scheduler 2. 添加 .storeDurably()3. 修正 Cron(注意 Quartz 支持秒段) |
| 任务只执行一次 | 1. SimpleTrigger 未设 repeatForever()2. Job 抛出未捕获异常导致 Trigger 失效 | 1. 检查 Trigger 构建代码 2. 查看日志是否有 JobExecutionException | 1. 添加 .repeatForever()2. 捕获异常并决定是否重试 |
| 集群下任务重复执行 | 1. 数据库表引擎非 InnoDB(无行锁) 2. 节点时间不同步 3. 未正确配置 isClustered=true | 1. 执行 SHOW CREATE TABLE QRTZ_LOCKS2. 检查各节点系统时间 3. 确认所有节点 quartz.properties 一致 | 1. 改为 InnoDB 2. 同步 NTP 时间 3. 统一配置文件 |
| 任务偶尔漏触发 | 1. misfireThreshold 过小2. 线程池满载,任务排队超时 | 1. 检查日志是否出现 “Handling misfired trigger” 2. 监控线程池活跃数 | 1. 增大 misfireThreshold2. 增加 threadCount 或优化 Job 性能 |
| 动态添加任务失败 | 1. JobKey 冲突 2. 未设置 storeDurably() | 1. 捕获 ObjectAlreadyExistsException2. 检查 QRTZ_JOB_DETAILS 是否残留旧任务 | 1. 先 checkExists() 再添加2. 务必调用 .storeDurably() |
| 应用重启后任务丢失 | 1. initialize-schema: always2. 使用 RAMJobStore | 1. 检查 application.yml2. 确认 job-store-type: jdbc | 1. 改为 initialize-schema: never2. 切换到 JDBCJobStore |
通用排查工具:
- 数据库查询:
- 查看待触发任务:
SELECT * FROM QRTZ_TRIGGERS WHERE state='WAITING' ORDER BY next_fire_time; - 查看正在执行任务:
SELECT * FROM QRTZ_FIRED_TRIGGERS;
- 查看待触发任务:
- 日志级别:开启
org.quartz=DEBUG可查看详细调度过程。 - 线程 Dump:若任务卡住,抓取线程栈分析是否死锁或阻塞。