schedule.ts — 调度计算引擎

核心函数

// 计算下一次运行时间
computeNextRunAtMs(schedule: CronSchedule, nowMs: number): number | undefined
 
// 计算上一次运行时间(仅 cron 模式)
computePreviousRunAtMs(schedule: CronSchedule, nowMs: number): number | undefined

三种模式的计算逻辑

at 模式

直接返回 at 字段的时间戳,若已过期则返回 undefined。

every 模式

基于锚点(anchorMs,默认 0)计算下一个周期:

nextRun = anchor + ceil((now - anchor) / everyMs) * everyMs

支持对齐到特定时间点,例如每小时整点对齐。

cron 模式

使用 croner 库解析 Cron 表达式,支持:

  • 标准 5 字段 Cron(* * * * *)
  • 时区(tz 字段)
  • 秒级精度(可选)

Bug 修复:处理 croner 的年份回滚 bug(某些时区/日期组合返回过去时间戳):

  1. 首次计算结果 < nowMs → 从下一秒重试
  2. 仍然失败 → 从明天 UTC 00:00 重新计算

LRU 缓存

const cronEvalCache = new LRUCache<string, Cron>({ max: 512 })
// 缓存键:`${timezone}\u0000${expr}`

避免重复解析相同的 Cron 表达式,最多缓存 512 条。


stagger.ts — 错开机制

目的

防止大量任务在整点(top-of-hour)同时触发,造成资源拥堵。

触发条件

检测到 0 * * * * 模式(每小时顶部)时自动应用错开。

实现原理

基于 jobId 的稳定哈希确定偏移量,保证同一任务每次偏移一致:

const digest = crypto.createHash("sha256").update(jobId).digest()
const offset = digest.readUInt32BE(0) % staggerMs
// 默认 staggerMs = 5 * 60 * 1000(5 分钟)

配置

  • 默认错开范围:5 分钟(DEFAULT_TOP_OF_HOUR_STAGGER_MS)
  • 可在 CronSchedule.staggerMs 中显式配置
  • 哈希缓存:避免重复计算

效果示例

10 个任务都配置 0 * * * *,实际触发时间分散在 00:00 ~ 05:00 之间,每个任务的偏移由其 ID 决定,稳定不变。


parse.ts — 时间解析

处理 at 模式的时间格式:

  • ISO 8601 字符串 → 内部统一为 ISO 格式
  • Unix 时间戳(毫秒)→ 转换为 ISO 字符串
  • 验证时间戳合法性(validate-timestamp.ts)

normalize-job-identity.ts — 任务标识规范化

计算任务调度的”身份哈希”(scheduleIdentity),用于检测调度配置是否变更:

  • 哈希输入:schedule + sessionTarget + payload 的关键字段
  • 变更时重新计算 nextRunAtMs
  • 存储在 jobs-state.json 中,避免重启后不必要的重新调度

错误退避调度

任务连续失败时,自动延长下次执行间隔(service/jobs.ts::errorBackoffMs):

DEFAULT_ERROR_BACKOFF_SCHEDULE_MS = [
  30_000,       // 第 1 次失败:30s 后重试
  60_000,       // 第 2 次失败:60s 后重试
  5 * 60_000,   // 第 3 次失败:5m 后重试
  15 * 60_000,  // 第 4 次失败:15m 后重试
  60 * 60_000,  // 第 5+ 次失败:1h 后重试
]

超过 scheduleErrorCount 阈值时自动禁用任务。

相关页面