← 返回文章列表

Cron 表达式完全指南:5 段与 6 段、日与周的坑

Cron避坑

两种主流格式

Linux/Unix 标准是 5 段:分 时 日 月 周;Quartz(Java/Spring 生态)是 6 段,前面多一个“秒”。动手前先确认目标平台用哪种,否则表达式整体错位。

特殊字符速查

  • * 任意值;
  • ? 不指定(只能用于“日”或“周”);
  • , 枚举:0 0 1,15 * * 每月 1 号和 15 号;
  • - 范围:0 9-18 * * * 每天 9 到 18 点;
  • / 步长:*/5 每 5 个单位。

最容易踩的两个坑

  1. “日”与“周”同时指定:多数实现按“或”处理——1 号或周一都会触发,与直觉相反。想表达“每月仅 1 号”,请把“周”写成 ?。
  2. 时区:Cron 按服务器时区执行。服务器在 UTC 而你在 UTC+8,想每天本地 9 点触发要写 0 1 * * *。

调试建议

与其反复部署验证,不如先用解析工具算出“接下来几次执行时间”,与预期逐条核对,几秒就能发现字段写反。

常用表达式速查

需求表达式(5 段)
每分钟* * * * *
每 5 分钟*/5 * * * *
每天 0 点0 0 * * *
工作日 9:3030 9 * * 1-5
每月 1 号 3 点0 3 1 * ?
每季度首日0 0 1 1,4,7,10 *

任务没按时执行?按这个顺序查

  1. 时区:容器默认 UTC,比北京时间晚 8 小时,本地 9 点实际要写 0 1 * * *;
  2. 守护进程:cron 服务没启动,或容器重启后 crontab 丢失;
  3. 字段数错位:平台要 6 段(Quartz)而你写了 5 段,或反过来;
  4. 秒位写成 *:* * * * * * 是每秒执行一次,容易把机器压垮;
  5. 没有日志:在命令后追加 >> /var/log/job.log 2>&1,先确认任务究竟有没有被触发。

上线前的最小验证流程

用解析工具算出“接下来 5 次执行时间”,与预期逐条核对,比部署后盯着日志试错快得多。跨时区场景尤其要核对:同一表达式在 UTC 与 UTC+8 下,触发时刻相差 8 小时。

上线前的最小核对清单

  1. 用解析工具确认接下来 3–5 次执行时间是否符合预期;
  2. 确认时区(容器通常默认 UTC)与业务时间一致;
  3. 确认任务具备幂等性:重复执行一次不会产生副作用,避免补偿时出问题;
  4. 确认日志落盘与告警,任务失败时能第一时间发现,而不是等用户反馈。

实战案例:三个高频事故

  1. “明明写了 9 点却中午才跑”:容器时区是 UTC,业务却按东八区理解。修复:把表达式改成 UTC 对应的 0 1 * * *,或显式设置容器时区(TZ=Asia/Shanghai)后重启。
  2. “每月 1 号跑了,周一又跑了一次”:把“日”和“周”都写成具体值,触发条件就变成了“或”。修复:只保留一个,另一个写 ?。
  3. “手动执行成功,cron 却不执行”:通常是环境变量与 PATH 不同——cron 不会加载你的 shell 配置。修复:脚本内用绝对路径,或显式 source 环境文件。

常见问题(FAQ)

“日”和“周”必须有一个是 ? 吗?不强制,但建议这么写;标准实现中两者同时存在时按“或”触发,容易与直觉不符。6 段是从秒开始吗?是的,Quartz 风格为 秒 分 时 日 月 周 [年],与 Linux 的 5 段不同。*/5 会从第 0 分钟开始吗?会,*/5 等价于 0,5,10,…。支持“每月最后一天”吗?标准 5 段不支持,需依赖平台扩展(如 Quartz 的 L)或用脚本判断。

动手试试:Cron 表达式解析、时间戳转换

cron 与 systemd timer 对照

  • 表达式差异:systemd 的 OnCalendar 用 *-*-* 09:00:00 这类可读写法,语义更直观但不可直接互转;
  • 错过是否补跑:cron 会直接跳过错过的时点,systemd 可用 Persistent=true 在关机期间错过后开机补跑;
  • 日志去向:cron 输出通常进系统邮件或 syslog,systemd 用 journalctl -u 查看,排障方式不同;
  • 秒级任务:cron 最小粒度为分钟,需要秒级请用 systemd timer 或应用内调度,别用 sleep 拼凑。

时区与夏令时

cron 按系统时区解释,容器里通常默认 UTC,这解释了“本地 9 点、线上中午跑”的经典问题。涉及夏令时的地区还会出现某天多跑或少跑一次,关键业务应显式设置 TZ 并避开 02:00–03:00 这段易受时间跳变影响的时间窗。

可读性建议

复杂表达式建议在代码里附带注释或用常量命名,例如把 */15 9-18 * * 1-5 命名为 EVERY_15_MIN_ON_WEEKDAYS_WORKING_HOURS。此外,能在代码中统一管理就别散落在各服务的配置文件里,否则改一次执行时间要翻遍所有仓库。