实战指南 5 分钟阅读

Cron 表达式详解:从五个字段到生产级定时任务

逐字段讲透 cron 语法,以及导致定时任务悄悄跑错的三大误区:时区、日字段的 OR 逻辑、错过的触发不会补跑。

Cron 从 1975 年跑到现在,五字段语法支撑着 Kubernetes CronJob、GitHub Actions、Spring 的 @Scheduled 和几十种 CI 系统。它也是生产事故的高发源头——而绝大多数事故来自三个误解,本文一次讲清。

五个字段,精确含义

┌──────── 分钟          (0–59)
│ ┌────── 小时          (0–23)
│ │ ┌──── 日            (1–31)
│ │ │ ┌── 月            (1–12)
│ │ │ │ ┌ 星期          (0–7,0 和 7 都是周日)
* * * * *

通用语法可以和任意字段组合:

  • * —— 每个值
  • */5 —— 每隔 5(分钟位即 :00、:05、:10…)
  • 1-5 —— 闭区间
  • 1,15,30 —— 列表
  • 1-10/2 —— 带步长的区间

所以 30 4 * * 1-5 是”工作日的 04:30”,0 9 1 * * 是”每月 1 号的 09:00”。

误区一:任务按服务器时区运行

最常见的 cron 惊喜。表达式按执行机器的本地时区求值,而云服务器通常是 UTC。0 9 * * *(“早上 9 点”)在 UTC 机器上对应上海时间凌晨 1 点。排查任何 cron 问题之前,先在真实机器上跑 date(Kubernetes 则看 CronJob 控制器的时区)。跨地区协作时,用时区转换工具对齐预期,并在平台允许时把调度时间显式锚定 UTC。

误区二:日和星期两个字段是 OR 关系

当两个字段都被约束(都不是 *)时,cron 按”任一匹配即触发”执行——是 OR 不是 AND。0 0 13 * 5 不是”13 号且是星期五才跑”,而是”每月 13 号跑一次 + 每个星期五跑一次”。这个行为写在 POSIX 规范里,几乎每个工程师都会惊讶一次。

Quartz 系调度器(Spring、部分任务平台)用另一种方式解决:不约束的字段写 ?——0 0 0 13 * ? 含义就无歧义了。

误区三:错过的窗口是跳过,不是补跑

机器宕机、进程挂起、或上一轮还没跑完就到点了——经典 cron 会直接跳过这次触发,不会事后补跑。对”漏跑有后果”的任务(每夜备份、账单生成),要么加”到底跑没跑”的对账,要么用带错失策略的调度器(systemd timer 的 Persistent=true、Kubernetes 的 startingDeadlineSeconds、Quartz 的 misfire 指令)。

排查流程:抓住 90% 的问题

  1. 解码表达式,看”未来 N 次执行时间”是否符合预期——Cron 工具支持 5 字段和 6 字段(含秒)写法并直接列出下次执行。
  2. 确认执行机时区,再按时区重读一遍表达式。
  3. 检查步长算术:分钟位的 */7 会在 :00、:07、:14…触发且跨小时不均匀——预期内是特性,预期外是事故。
  4. 上线前在预发真实触发一次,盯完整流程再放行。

Cron 是门五十年历史的老手艺,“cron 出 bug”大多是时区和语义的误解——用对工具,每一条都能在一分钟内核查清楚。