2. Quartz集群架构:集群部署模式、负载均衡策略、故障转移机制、数据库表结构概览
Quartz 集群架构是保障生产调度高可用的核心设计。本章从部署模式、负载均衡、故障转移和底层表结构四个维度展开,帮助巡检人员建立集群视角的索引与连接池分析基础。
2.1 集群部署模式
Quartz 支持两种集群部署模式:纯 JDBC 模式(最常用)和 Terracotta 模式。生产环境几乎全部采用 JDBC 模式,通过共享数据库实现节点间协调。
| 特性 | JDBC 集群模式 | Terracotta 集群模式 |
|---|---|---|
| 协调方式 | 数据库行锁(悲观锁) | Terracotta 服务端内存协调 |
| 节点数量 | 建议 2~10 个 | 可支持更多节点 |
| 数据库依赖 | 强依赖(所有调度状态持久化) | 弱依赖(可选持久化) |
| 运维复杂度 | 低(仅需共享数据库) | 高(需维护 Terracotta 服务) |
| 生产推荐度 | ★★★★★ | ★★☆☆☆ |
巡检提示:JDBC 集群模式下,数据库连接池水位和 QRTZ_LOCKS 表的行锁等待是集群健康度的关键指标。后续章节将专门讲解锁表监控。
2.2 负载均衡策略
Quartz 集群的负载均衡并非传统意义上的请求分发,而是基于数据库锁的节点竞争机制。每个节点在触发时间窗口内,通过获取 QRTZ_TRIGGERS 表中的行锁来抢占待执行触发器。
- 抢占式调度:所有节点同时扫描
STATE_WAITING状态的触发器,谁先获得锁谁执行。 - 无中心协调器:不存在主节点选举,节点间完全对等。
- 自然负载分散:当触发器数量远大于节点数时,各节点获取的触发器数量大致均衡。
- 权重差异:可通过
org.quartz.threadPool.threadCount调整单个节点的并发处理能力,间接影响负载分布。
关键参数:
org.quartz.scheduler.idleWaitTime(默认 30000ms)控制节点扫描数据库的频率。值越小,节点竞争越激烈,数据库压力越大;值越大,调度延迟可能增加。巡检时需关注该参数与连接池大小的匹配度。
2.3 故障转移机制
Quartz 的故障转移依赖 节点心跳 + 数据库记录 实现。每个节点定期更新 QRTZ_SCHEDULER_STATE 表中的 LAST_CHECKIN_TIME 字段。
- 心跳检测:节点每
checkinInterval(默认 15000ms)更新一次心跳时间戳。 - 超时判定:其他节点扫描时,若发现某节点的心跳时间超过
checkinInterval * misfireThreshold(默认 60000ms),则判定该节点失效。 - 触发器接管:失效节点持有的
QRTZ_FIRED_TRIGGERS记录会被其他节点回收,重新标记为待执行状态。 - 恢复执行:接管节点根据 misfire 策略(如
MISFIRE_INSTRUCTION_FIRE_NOW)重新触发任务。
巡检风险点:若
misfireThreshold 设置过小,网络抖动可能导致误判节点失效,引发不必要的任务重复执行。建议结合网络延迟情况适当放宽阈值。
2.4 数据库表结构概览
Quartz JDBC 集群依赖 11 张核心表。巡检时需重点关注以下 6 张高频访问表,它们直接决定了索引选择和连接池水位。
| 表名 | 核心字段 | 访问频率 | 巡检关注点 |
|---|---|---|---|
QRTZ_TRIGGERS |
SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, NEXT_FIRE_TIME, TRIGGER_STATE | 极高(每次调度扫描) | NEXT_FIRE_TIME 索引选择性;TRIGGER_STATE 分布 |
QRTZ_FIRED_TRIGGERS |
SCHED_NAME, ENTRY_ID, FIRED_TIME, INSTANCE_NAME | 高(节点抢占与释放) | INSTANCE_NAME 索引;记录数是否异常堆积 |
QRTZ_SCHEDULER_STATE |
SCHED_NAME, INSTANCE_NAME, LAST_CHECKIN_TIME | 中(心跳更新) | LAST_CHECKIN_TIME 更新延迟;节点数是否匹配 |
QRTZ_LOCKS |
SCHED_NAME, LOCK_NAME | 极高(每次调度加锁) | 锁等待次数;行锁竞争是否导致连接池阻塞 |
QRTZ_JOB_DETAILS |
SCHED_NAME, JOB_NAME, JOB_GROUP | 低(任务注册/更新时) | JOB_NAME 唯一性;描述字段是否过大 |
QRTZ_CRON_TRIGGERS |
SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, CRON_EXPRESSION | 中(Cron 触发器解析) | CRON_EXPRESSION 长度;与 QRTZ_TRIGGERS 的 JOIN 效率 |
2.5 核心表关系与索引策略
Quartz 的调度查询核心 SQL 通常涉及 QRTZ_TRIGGERS 与 QRTZ_CRON_TRIGGERS(或 QRTZ_SIMPLE_TRIGGERS)的 JOIN。默认索引设计如下:
-- QRTZ_TRIGGERS 默认主键索引(复合)
PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
-- 关键辅助索引:用于扫描待执行触发器
INDEX IDX_QRTZ_T_NEXT_FIRE_TIME (SCHED_NAME, NEXT_FIRE_TIME)
INDEX IDX_QRTZ_T_NFT_ST (SCHED_NAME, NEXT_FIRE_TIME, TRIGGER_STATE)
INDEX IDX_QRTZ_T_NFT_MISFIRE (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME)
-- QRTZ_FIRED_TRIGGERS 索引
INDEX IDX_QRTZ_FT_INST_NAME (SCHED_NAME, INSTANCE_NAME)
INDEX IDX_QRTZ_FT_TRIG_NAME (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
索引选择要点:Quartz 默认的
IDX_QRTZ_T_NFT_ST 索引在大多数场景下已足够。但当集群节点数超过 5 个且触发器数量超过 10 万时,TRIGGER_STATE 字段的区分度下降(大量 'WAITING' 状态),此时可考虑将 NEXT_FIRE_TIME 作为前导列的索引,避免状态字段的过滤失效。
2.6 集群架构下的连接池水位特征
Quartz 集群对数据库连接池的消耗呈现明显的周期性脉冲特征:
- 调度扫描脉冲:每
idleWaitTime间隔,所有节点同时发起扫描查询,瞬间拉高连接池使用率。 - 锁竞争脉冲:获取
QRTZ_LOCKS行锁时,等待中的连接会阻塞在数据库层,导致连接池连接被长时间占用。 - 心跳更新脉冲:每
checkinInterval所有节点同时更新心跳,产生小批量写操作。
巡检时建议监控连接池的 活跃连接数曲线,若在调度扫描时刻出现尖峰且超过连接池上限的 70%,则需要考虑优化 idleWaitTime 或增加连接池大小。
巡检建议:将连接池的
maxActive 设置为 节点数 × 3 + 5 作为初始值,再根据实际脉冲峰值微调。例如 3 节点集群,初始 maxActive = 3×3+5 = 14。