JOURNAL / WORDS & IDEAS

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 三剑客)

  1. Job (任务):
    • 做什么。实现 org.quartz.Job 接口,重写 execute() 方法。
    • Demo 实践:定义一个 BaseJob 接口,所有业务 Job 继承它,方便统一管理。
  2. Trigger (触发器):
    • 什么时候做。常用 CronTrigger (基于 Cron 表达式)。
    • 包含:Cron 表达式、时区、启动时间等。
  3. Scheduler (调度器):
    • 总管。负责将 Job 和 Trigger 注册到一起,并启动调度。
    • API:scheduler.scheduleJob(jobDetail, trigger)。

4. 动态管理逻辑 (CRUD)

Quartz 的强大在于可以在不重启服务的情况下管理任务。

A. 新增任务 (Add)

  1. 反射创建 Job: 使用 Class.forName(className).newInstance() 获取 Job 实例(如 JobUtil.java)。
  2. 构建 JobDetail: JobBuilder.newJob(clazz).withIdentity(name, group).build()。
  3. 构建 Trigger: TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule(cron)).build()。
  4. 注册: scheduler.scheduleJob(jobDetail, trigger)。
  5. 持久化: Quartz 自动将信息写入 QRTZ_ 表。

B. 暂停/恢复 (Pause/Resume)

  • 暂停: scheduler.pauseJob(JobKey.jobKey(name, group))。
    • 状态变化: WAITING -> PAUSED。
  • 恢复: scheduler.resumeJob(JobKey.jobKey(name, group))。
    • 状态变化: PAUSED -> WAITING。
  • 场景: 系统维护、业务高峰期降级。

C. 修改时间 (Update Cron)

  1. 获取旧 Trigger: scheduler.getTrigger(TriggerKey.triggerKey(name, group))。
  2. 构建新 Trigger: 使用新的 Cron 表达式重建 Trigger,但保持相同的 Key。
  3. 重置: scheduler.rescheduleJob(triggerKey, newTrigger)。

D. 删除任务 (Delete)

  1. 暂停: 先 pause。
  2. 卸载: scheduler.unscheduleJob(triggerKey)。
  3. 删除: scheduler.deleteJob(jobKey)。
  4. 清理: 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)

  1. 集群重复执行:
    • ✅ 必须配置 org.quartz.jobStore.acquireTriggersWithinLock: true。
    • ✅ 确保所有集群节点的时间同步 (NTP)。
  2. 异常处理:
    • Quartz 任务中抛出的异常不会中断 Scheduler,但会导致本次执行失败。
    • ✅ 务必在 execute() 方法内部 try-catch 并记录日志。
  3. 长耗时任务:
    • 如果任务执行时间 > threadCount 的周转时间,会导致线程池耗尽,后续任务全部延迟。
    • ✅ 优化:任务内只发消息 (MQ),由消费者异步处理;或增大 threadCount。
  4. Misfire (错失触发):
    • 如果服务器宕机重启,错过了任务执行时间。
    • ✅ 配置 misfireThreshold,并在 Trigger 中设置 Misfire 指令(如 withMisfireHandlingInstructionDoNothing 忽略,或 FireNow 立即补一次)。

7. 快速复习口诀

  • 要持久,选 JDBC,建表脚本不能缺。
  • 防重复,加把锁,acquireTriggers 设 true。
  • 动态改,用 API,增删改查不停机。
  • 反射类,全名传,Class.forName 来实例。
  • 集群版,XXL-JOB,Quartz 适合小而美。

这份笔记涵盖了你当前 Demo 的核心逻辑以及生产环境的关键注意点,建议保存以备查阅!

集群与任务入口

acquireTriggersWithinLock 不是完整的集群开关;集群还需共享数据库、isClustered、实例标识和一致的调度配置。禁止把任意前端输入当作类名直接反射执行,动态任务应映射到服务端白名单。@Scheduled 也可以借助调度 API 做动态管理,不能简单说它完全不支持。