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% 的问题
- 解码表达式,看”未来 N 次执行时间”是否符合预期——Cron 工具支持 5 字段和 6 字段(含秒)写法并直接列出下次执行。
- 确认执行机时区,再按时区重读一遍表达式。
- 检查步长算术:分钟位的
*/7会在 :00、:07、:14…触发且跨小时不均匀——预期内是特性,预期外是事故。 - 上线前在预发真实触发一次,盯完整流程再放行。
Cron 是门五十年历史的老手艺,“cron 出 bug”大多是时区和语义的误解——用对工具,每一条都能在一分钟内核查清楚。