3. Quartz 集群原理:集群架构、数据库锁机制、负载均衡与故障转移

Quartz 集群模式通过将多个调度器实例(Scheduler Node)注册到同一个数据库(JobStore),实现任务调度的去中心化与高可用。本章深入剖析集群的底层架构、锁机制、负载均衡策略以及故障转移流程,帮助你在迁移平台中正确配置与运维集群。

3.1 集群架构:无中心对等节点

Quartz 集群采用 无中心对等架构,所有节点地位平等,不依赖主节点选举。每个节点独立运行,通过共享数据库协同工作。

核心设计原则: 所有节点同时运行,但同一时间只有一个节点能执行同一个任务(Job)。数据库是唯一的“仲裁者”。
Quartz 集群无中心对等架构 共享数据库 QRTZ_TRIGGERS / QRTZ_FIRED_TRIGGERS Scheduler Node 1 实例名: S1 状态: 运行中 Scheduler Node 2 实例名: S2 状态: 运行中 Scheduler Node 3 实例名: S3 状态: 运行中 架构特点 • 所有节点通过 JDBC 连接同一数据库 • 无主节点,无选举开销,节点对等

3.2 数据库锁机制:行级锁与状态机

Quartz 集群的并发控制完全依赖数据库锁,而非 Java 内存锁。核心锁机制分为两类:

锁类型 表名 作用 锁粒度
TRIGGER_ACCESS QRTZ_LOCKS 保护触发器获取与更新流程 行级(单行锁)
STATE_ACCESS QRTZ_LOCKS 保护调度器实例状态注册与心跳 行级(单行锁)
关键机制: Quartz 使用 SELECT ... FOR UPDATE 语句获取行级排他锁。当一个节点获取锁后,其他节点必须等待锁释放,从而保证同一时刻只有一个节点能操作触发器状态。

3.2.1 锁获取流程

  1. 节点尝试获取锁: 执行 SELECT * FROM QRTZ_LOCKS WHERE LOCK_NAME = 'TRIGGER_ACCESS' FOR UPDATE。
  2. 数据库阻塞: 如果锁已被其他节点持有,当前节点进入等待状态,直到锁释放或超时。
  3. 锁超时处理: 默认超时时间由 org.quartz.jobStore.acquireTriggersWithinLock 控制,超时后节点放弃本次调度周期,等待下一轮。
  4. 锁释放: 节点完成触发器获取、更新状态后,提交事务释放锁。
-- Quartz 锁表示例数据
LOCK_NAME          | LOCK_OWNER
-------------------|-------------------
TRIGGER_ACCESS     | S1-NODE-12345
STATE_ACCESS       | S1-NODE-12345

3.3 负载均衡:基于“谁先抢到谁执行”

Quartz 集群的负载均衡策略是 抢占式 的,而非轮询或权重分配。每个节点在调度周期内都会尝试从数据库获取待执行的触发器。

核心规则: 所有节点同时运行 acquireNextTriggers() 方法,数据库锁确保只有一个节点能成功获取一组触发器。获取成功的节点负责执行这些任务,其他节点等待下一轮。

3.3.1 负载均衡参数

参数名 默认值 说明
org.quartz.scheduler.instanceId AUTO 集群中每个节点的唯一标识,用于区分锁持有者
org.quartz.jobStore.acquireTriggersWithinLock true 是否在锁内获取触发器,集群模式必须为 true
org.quartz.jobStore.maxMisfiresToHandleAtATime 20 单次处理的最大 misfire 数量,影响负载分布

3.4 故障转移:心跳检测与自动恢复

Quartz 集群通过 心跳机制 检测节点故障,并自动将故障节点持有的任务重新分配给其他节点。

3.4.1 心跳流程

  1. 定期更新心跳: 每个节点每隔 org.quartz.jobStore.clusterCheckinInterval(默认 7500ms)更新 QRTZ_SCHEDULER_STATE 表中的 LAST_CHECKIN_TIME 字段。
  2. 检测故障: 其他节点在获取锁时,检查 QRTZ_SCHEDULER_STATE 表中是否有节点的 LAST_CHECKIN_TIME 超过 CHECKIN_TIMEOUT(默认 20000ms)。
  3. 标记故障: 如果某个节点超时未更新心跳,其他节点认为该节点已宕机。
  4. 任务接管: 故障节点持有的 FIRED_TRIGGERS 记录会被清理,其正在执行的任务变为 misfire 状态,由其他节点在下一轮调度中重新获取执行。
Quartz 故障转移流程 Node 1 (正常) 心跳正常 Node 2 (故障) 心跳超时 共享数据库 检测超时 故障转移步骤 1. Node 2 停止更新 QRTZ_SCHEDULER_STATE 表 2. Node 1 在获取 TRIGGER_ACCESS 锁时,发现 Node 2 心跳超时 3. Node 1 清理 Node 2 的 FIRED_TRIGGERS 记录 4. Node 1 将 Node 2 未完成的任务标记为 misfire 5. Node 1 在下一调度周期重新获取并执行这些任务

3.5 集群配置关键参数

参数名 推荐值 说明
org.quartz.jobStore.isClustered true 启用集群模式
org.quartz.jobStore.clusterCheckinInterval 7500 心跳间隔(毫秒),建议 5000~15000
org.quartz.scheduler.instanceId AUTO 自动生成唯一实例 ID
org.quartz.jobStore.acquireTriggersWithinLock true 集群模式必须为 true
org.quartz.jobStore.tablePrefix QRTZ_ Quartz 表前缀
避坑提示: 集群节点数不宜过多(建议 3~5 个),因为所有节点竞争同一数据库锁,节点过多会导致锁竞争加剧,反而降低调度吞吐量。另外,clusterCheckinInterval 必须小于 CHECKIN_TIMEOUT(默认 20000ms),否则可能误判节点故障。

3.6 集群状态表结构

Quartz 集群依赖以下核心表实现状态管理:

表名 关键字段 作用
QRTZ_SCHEDULER_STATE INSTANCE_NAME, LAST_CHECKIN_TIME 记录每个节点的最后心跳时间
QRTZ_LOCKS LOCK_NAME, LOCK_OWNER 存储锁记录,用于并发控制
QRTZ_FIRED_TRIGGERS ENTRY_ID, INSTANCE_NAME, FIRED_TIME 记录当前正在执行的触发器
QRTZ_TRIGGERS TRIGGER_NAME, TRIGGER_GROUP, NEXT_FIRE_TIME 存储触发器定义与下次执行时间

理解这些表的结构与交互逻辑,是排查集群问题(如任务重复执行、节点假死、锁死锁)的基础。