Spring Boot 与 Quartz:任务调度核心概念
整理 Job、Trigger、持久化、并发与动态任务管理的基础知识。
阅读设置
这是一份关于 Spring Boot 整合 Quartz 实现分布式/持久化定时任务 的复习笔记。
📚 Spring Boot + Quartz 核心知识点笔记
1. 为什么选择 Quartz?(vs @Scheduled)
| 特性 | @Scheduled (Spring自带) |
Quartz |
|---|---|---|
| 存储方式 | 内存 (重启丢失) | 数据库 (JDBC) / 内存 |
| 集群支持 | ❌ 不支持 (多实例会重复执行) | ✅ 支持 (通过 DB 锁防止重复) |
| 动态管理 | ❌ 需修改代码并重启 | ✅ 支持 (运行时增删改查、暂停恢复) |
| 复杂调度 | 仅支持 Cron/FixedRate | ✅ 支持复杂的 Trigger、Calendar、Misfire 策略 |
| 适用场景 | 单机、简单、低频任务 | 集群、高可用、需动态管理、需要业务验证的任务 |
2. 核心配置 (application.yml)
A. 开启 JDBC 存储 (持久化关键)
spring:
quartz:
job-store-type: jdbc # 必须设为 jdbc,否则默认是内存
wait-for-jobs-to-complete-on-shutdown: true # 优雅停机:等任务跑完再关闭
scheduler-name: MyScheduler
properties:
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# ⭐ 核心防重配置:获取触发器时加锁,防止集群多节点同时抢到同一个任务
org.quartz.jobStore.acquireTriggersWithinLock: true
- 前置条件:必须在数据库中执行 Quartz 官方提供的 SQL 脚本,创建 11 张
QRTZ_开头的表。
B. 线程池配置
org.quartz.threadPool.threadCount: 5 # 同时能跑几个任务
- 注意:如果任务耗时且多,调大此值;否则任务会排队等待。
3. 核心概念模型 (Quartz 三剑客)
- Job (任务):
- 做什么。实现
org.quartz.Job接口,重写execute()方法。 - Demo 实践:定义一个
BaseJob接口,所有业务 Job 继承它,方便统一管理。
- 做什么。实现
- Trigger (触发器):
- 什么时候做。常用
CronTrigger(基于 Cron 表达式)。 - 包含:Cron 表达式、时区、启动时间等。
- 什么时候做。常用
- Scheduler (调度器):
- 总管。负责将 Job 和 Trigger 注册到一起,并启动调度。
- API:
scheduler.scheduleJob(jobDetail, trigger)。
4. 动态管理逻辑 (CRUD)
Quartz 的强大在于可以在不重启服务的情况下管理任务。
A. 新增任务 (Add)
- 反射创建 Job: 使用
Class.forName(className).newInstance()获取 Job 实例(如JobUtil.java)。 - 构建 JobDetail:
JobBuilder.newJob(clazz).withIdentity(name, group).build()。 - 构建 Trigger:
TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule(cron)).build()。 - 注册:
scheduler.scheduleJob(jobDetail, trigger)。 - 持久化: Quartz 自动将信息写入
QRTZ_表。
B. 暂停/恢复 (Pause/Resume)
- 暂停:
scheduler.pauseJob(JobKey.jobKey(name, group))。- 状态变化:
WAITING->PAUSED。
- 状态变化:
- 恢复:
scheduler.resumeJob(JobKey.jobKey(name, group))。- 状态变化:
PAUSED->WAITING。
- 状态变化:
- 场景: 系统维护、业务高峰期降级。
C. 修改时间 (Update Cron)
- 获取旧 Trigger:
scheduler.getTrigger(TriggerKey.triggerKey(name, group))。 - 构建新 Trigger: 使用新的 Cron 表达式重建 Trigger,但保持相同的 Key。
- 重置:
scheduler.rescheduleJob(triggerKey, newTrigger)。
D. 删除任务 (Delete)
- 暂停: 先 pause。
- 卸载:
scheduler.unscheduleJob(triggerKey)。 - 删除:
scheduler.deleteJob(jobKey)。 - 清理: Quartz 自动从数据库删除相关记录。
5. 关键工具与规范
A. 统一响应封装 (ApiResponse)
- 所有 Controller 返回
ResponseEntity<ApiResponse>。 - 区分 HTTP 状态码 (200, 400, 500) 和业务状态。
B. 参数校验 (JobForm)
- 使用
@NotBlank确保jobClassName,jobGroupName,cronExpression不为空。 - 使用 DTO (Form) 隔离前端输入与后端 Entity,提高安全性。
C. 反射工具 (JobUtil)
Class.forName(className):根据前端传来的字符串类名,动态加载 Class。- 注意:类名必须是全限定名(包名+类名),且该类必须在 Classpath 下。
6. 避坑指南 (Production Ready)
- 集群重复执行:
- ✅ 必须配置
org.quartz.jobStore.acquireTriggersWithinLock: true。 - ✅ 确保所有集群节点的时间同步 (NTP)。
- ✅ 必须配置
- 异常处理:
- Quartz 任务中抛出的异常不会中断 Scheduler,但会导致本次执行失败。
- ✅ 务必在
execute()方法内部try-catch并记录日志。
- 长耗时任务:
- 如果任务执行时间 >
threadCount的周转时间,会导致线程池耗尽,后续任务全部延迟。 - ✅ 优化:任务内只发消息 (MQ),由消费者异步处理;或增大
threadCount。
- 如果任务执行时间 >
- Misfire (错失触发):
- 如果服务器宕机重启,错过了任务执行时间。
- ✅ 配置
misfireThreshold,并在 Trigger 中设置 Misfire 指令(如withMisfireHandlingInstructionDoNothing忽略,或FireNow立即补一次)。
7. 快速复习口诀
- 要持久,选 JDBC,建表脚本不能缺。
- 防重复,加把锁,
acquireTriggers设 true。 - 动态改,用 API,增删改查不停机。
- 反射类,全名传,
Class.forName来实例。 - 集群版,XXL-JOB,Quartz 适合小而美。
这份笔记涵盖了你当前 Demo 的核心逻辑以及生产环境的关键注意点,建议保存以备查阅!
集群与任务入口
acquireTriggersWithinLock 不是完整的集群开关;集群还需共享数据库、isClustered、实例标识和一致的调度配置。禁止把任意前端输入当作类名直接反射执行,动态任务应映射到服务端白名单。@Scheduled 也可以借助调度 API 做动态管理,不能简单说它完全不支持。