Cron 表达式的字段与通配符
标准的 Linux crontab 是 5 段:分(0-59)、时(0-23)、日(1-31)、月(1-12)、周(0-7,0 和 7 都表示周日)。Java 生态的 Quartz 以及 Spring 的 @Scheduled 多用 6 段,在最前面多一个秒字段。每段可以写四种形式:星号表示该字段的所有取值;逗号列举多个值,如 8,12,18;短横线表示连续区间,如 1-5;斜线表示步长,如 */5 表示从 0 开始每 5 个单位一次,10-20/2 表示在区间内每 2 个单位一次。
最容易出错的是日和周这两个字段的关系。在标准 crontab 中,当日与周都不是星号时,两者是“或”的关系,只要满足其中一个就执行;例如 0 0 1 * 1 表示每月 1 日以及每周一都会在 0 点执行,而不是“1 号且是周一”。只有一个字段被限制时,就按那个字段匹配。Quartz 用问号来表示“不指定”,避免这种歧义,本工具把问号按星号处理。
使用这个解析器时的注意事项
工具先把每个字段展开成具体的取值集合,再从当前时刻开始逐个跳到下一个候选时间点,因此推算结果与真实调度器一致,也能正确处理二月、闰年和月末。推算使用的是你电脑的本地时区,而服务器上的定时任务按服务器时区运行,很多线上事故就是因为服务器是 UTC 而开发者按北京时间理解表达式,相差八小时。
另外几个常见坑:步长的起点是区间的起点而不是当前时间,所以 */7 在小时字段上会在 0、7、14、21 点触发,最后一段间隔只有 3 小时;写 30 日或 31 日的任务,在没有该日期的月份会被跳过;分钟字段写星号意味着每分钟执行一次,配合耗时较长的脚本容易堆积进程,建议加文件锁。修改 crontab 后用 crontab -l 确认,并让脚本把输出重定向到日志,否则失败时无从排查。