3. Quartz 集群原理:集群架构、数据库锁机制、负载均衡与故障转移
Quartz 集群模式通过将多个调度器实例(Scheduler Node)注册到同一个数据库(JobStore),实现任务调度的去中心化与高可用。本章深入剖析集群的底层架构、锁机制、负载均衡策略以及故障转移流程,帮助你在迁移平台中正确配置与运维集群。
3.1 集群架构:无中心对等节点
Quartz 集群采用 无中心对等架构,所有节点地位平等,不依赖主节点选举。每个节点独立运行,通过共享数据库协同工作。
核心设计原则: 所有节点同时运行,但同一时间只有一个节点能执行同一个任务(Job)。数据库是唯一的“仲裁者”。
3.2 数据库锁机制:行级锁与状态机
Quartz 集群的并发控制完全依赖数据库锁,而非 Java 内存锁。核心锁机制分为两类:
| 锁类型 | 表名 | 作用 | 锁粒度 |
|---|---|---|---|
| TRIGGER_ACCESS | QRTZ_LOCKS | 保护触发器获取与更新流程 | 行级(单行锁) |
| STATE_ACCESS | QRTZ_LOCKS | 保护调度器实例状态注册与心跳 | 行级(单行锁) |
关键机制: Quartz 使用
SELECT ... FOR UPDATE 语句获取行级排他锁。当一个节点获取锁后,其他节点必须等待锁释放,从而保证同一时刻只有一个节点能操作触发器状态。
3.2.1 锁获取流程
- 节点尝试获取锁: 执行
SELECT * FROM QRTZ_LOCKS WHERE LOCK_NAME = 'TRIGGER_ACCESS' FOR UPDATE。 - 数据库阻塞: 如果锁已被其他节点持有,当前节点进入等待状态,直到锁释放或超时。
- 锁超时处理: 默认超时时间由
org.quartz.jobStore.acquireTriggersWithinLock控制,超时后节点放弃本次调度周期,等待下一轮。 - 锁释放: 节点完成触发器获取、更新状态后,提交事务释放锁。
-- 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 心跳流程
- 定期更新心跳: 每个节点每隔
org.quartz.jobStore.clusterCheckinInterval(默认 7500ms)更新QRTZ_SCHEDULER_STATE表中的LAST_CHECKIN_TIME字段。 - 检测故障: 其他节点在获取锁时,检查
QRTZ_SCHEDULER_STATE表中是否有节点的LAST_CHECKIN_TIME超过CHECKIN_TIMEOUT(默认 20000ms)。 - 标记故障: 如果某个节点超时未更新心跳,其他节点认为该节点已宕机。
- 任务接管: 故障节点持有的
FIRED_TRIGGERS记录会被清理,其正在执行的任务变为misfire状态,由其他节点在下一轮调度中重新获取执行。
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 | 存储触发器定义与下次执行时间 |
理解这些表的结构与交互逻辑,是排查集群问题(如任务重复执行、节点假死、锁死锁)的基础。