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(某些时区/日期组合返回过去时间戳):
- 首次计算结果 < nowMs → 从下一秒重试
- 仍然失败 → 从明天 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 阈值时自动禁用任务。
相关页面
- cron-overview — 模块总览
- cron-types — 数据结构(CronSchedule)
- cron-service — 服务层(定时器执行)