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 字段。

  1. 心跳检测:节点每 checkinInterval(默认 15000ms)更新一次心跳时间戳。
  2. 超时判定:其他节点扫描时,若发现某节点的心跳时间超过 checkinInterval * misfireThreshold(默认 60000ms),则判定该节点失效。
  3. 触发器接管:失效节点持有的 QRTZ_FIRED_TRIGGERS 记录会被其他节点回收,重新标记为待执行状态。
  4. 恢复执行:接管节点根据 misfire 策略(如 MISFIRE_INSTRUCTION_FIRE_NOW)重新触发任务。
Quartz 集群故障转移流程 节点 A(正常) 心跳正常 节点 B(故障) 心跳超时 共享数据库 QRTZ_SCHEDULER_STATE 定期 checkin 心跳中断 步骤 2:节点 A 扫描数据库,发现节点 B 的 LAST_CHECKIN_TIME 超时 步骤 3:节点 A 回收节点 B 的 FIRED_TRIGGERS 记录,标记为待执行 步骤 4:节点 A 按 misfire 策略重新触发任务,完成故障转移
巡检风险点:若 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 或增加连接池大小。

Quartz 集群连接池水位脉冲示意 时间轴(秒) 活跃连接数 基线 10 调度扫描脉冲 锁竞争脉冲 心跳更新脉冲 连接池上限 30
巡检建议:将连接池的 maxActive 设置为 节点数 × 3 + 5 作为初始值,再根据实际脉冲峰值微调。例如 3 节点集群,初始 maxActive = 3×3+5 = 14。