定时任务没按时跑,排查半天发现是表达式少了一个星号;或者明明写的是“每周日”,任务却在周六触发。Cron 语法只有五个字段,坑却不少。
这篇按“字段、符号、语法变体、高频坑”的顺序讲,最后给一张常用表达式对照表。
五个字段,从分钟到星期
先记住两个前提:字段从左到右、时间粒度从细到粗,星号表示“每一个”;写之前确认目标平台认几段语法,这是最容易出错的地方。
- 分钟 0-59、小时 0-23、日 1-31、月 1-12
- 星期:Linux 和 Spring 用 0-7,0 和 7 都表示周日;Quartz 用 1-7,1 表示周日
- 六段语法在最前面多一个“秒”(0-59),Spring 常用;Quartz 七段再在最后多一个“年”
- 例:0 9 * * 1-5 → 周一到周五 09:00
- 例:*/5 * * * * → 每 5 分钟;0 0 1 * * → 每月 1 日 00:00
特殊符号
特殊符号分两套:基础符号通用,? L W # 只有 Quartz 支持。站内 Cron 推算会依次尝试 UNIX 五段、Spring 六段(含秒)、Quartz 七段语法,解析成功后会显示识别到的类型,方便确认自己写的是哪一套。
- * 任意值;, 枚举(1,3,5);- 范围(1-5);/ 步长(*/10、0-30/5)
- ? 仅用于日或星期,表示“不指定”(Quartz 语法,二者必须有一个是 ?)
- L 表示月末或最后一个星期几;W 表示最近的工作日;# 表示第几个星期几(如 6#3 是第三个周五)
- 注意:? L W # 不是 Linux crontab 语法,写进 crontab 会直接报错
日和星期同时限制:最容易踩的语义坑
在 Linux cron 里,如果“日”和“星期”都不是星号,任务会在“满足任意一个”时触发,而不是两个都满足。例如 0 0 1 * 1 不是“每月 1 日且是周一”,而是“每月 1 日或每周一”。
- 想表达“每月 1 日,且只在周一时执行”:cron 本身做不到,需要在脚本里判断
- Quartz 用 ? 强制互斥:0 0 0 1 * ? 表示每月 1 日,不限制星期
- 大多数“每月 1 日”的需求应该写 0 0 1 * *,把星期留成星号
- 不同实现略有差异,部署前用目标平台的解析器验证一遍
时区、夏令时与执行环境
时区和执行环境是最容易被忽略的部分。站内推算按服务器本地时区计算,输出未来 N 次触发时间;跨时区部署时,先在目标时区验证,不要拿本地开发机的结果当准。
- crontab 使用系统时区;容器镜像默认常是 UTC,比北京时间晚 8 小时
- Kubernetes CronJob 默认 UTC,需要显式设置 spec.timeZone,例如 Asia/Shanghai
- 夏令时切换时,凌晨的任务可能被跳过或执行两次;中国无夏令时,但海外服务器要注意
- cron 不保证“只执行一次”:任务重叠、机器重启、多副本部署都可能导致重复执行,需要幂等和锁
常用表达式对照
下面是最常用的几条。写完别靠肉眼检查:用推算工具看未来 10 次触发时间,重点看月末、跨年、闰年和时区边界。
- 0 9 * * 1-5 → 工作日 9:00
- 0 0 * * * → 每天 0:00
- 0 3 * * 0 → 每周日 3:00(0 也可写 7)
- 0 0 1 * * → 每月 1 日 0:00
- 0 0 1 1 * → 每年 1 月 1 日 0:00
- */10 * * * * → 每 10 分钟
- 0 */2 * * * → 每 2 小时
- 0 9-18 * * 1-5 → 工作日 9 点到 18 点整点
- 0 0 29 2 * → 闰年 2 月 29 日(非闰年不触发)
常见问题
周日到底写 0 还是 7?
Linux cron 两者都表示周日,等价;Spring 的六段语法同样是 0-7。Quartz 的星期是 1-7 且 1 表示周日,不能照搬 0/7 的写法。
为什么六段表达式在 crontab 里不生效?
多出来的第一段是“秒”,属于 Spring 或 Quartz 语法;Linux crontab 只认五段,秒级需求要用别的调度器。
每天凌晨 2 点跑,为什么有时不跑?
检查三件事:时区(容器常为 UTC)、夏令时跳变、以及上一次任务是否还没结束。任务重叠时,不同实现的处理方式可能不同。