/5 * * * 是最常被复制粘贴的 cron 表达式之一。很多人以为它的含义是「从现在开始,每隔 5 分钟执行一次」。按这个理解,如果配置的时刻是 10 点 03 分,任务应该在 08 分、13 分、18 分执行。但实际观察到的是 05 分、10 分、15 分——间隔确实接近 5 分钟,但起点对不上。
步长是从范围起点算的
/ 的含义是「从范围的起点开始,每隔 N 个单位取一个值」。/5 中的 * 表示完整范围 0 到 59,所以 /5 等价于 0-59/5,展开后的取值是 0、5、10、15、…、55。
这些是固定的时刻,与配置时间无关。无论你在什么时候保存这份 crontab,任务都会在整点之后的第 0、5、10 分钟执行。
理解这一点之后,0-30/10 的含义也就清楚了:从 0 开始每隔 10 取一个值,得到 0、10、20、30,也就是「每小时的前半小时内每 10 分钟一次」。
边界处的间隔会不均匀
步长不是「间隔」,而是「起点加倍数」,这个区别在范围不能整除时会产生不均匀的间隔。
以分钟字段的 /7 为例,取值是 0、7、14、21、28、35、42、49、56。最后一次执行在 56 分,下一次是本小时的 0 分——间隔只有 4 分钟,而不是 7 分钟。小时字段的 /5 同理,取 0、5、10、15、20,最后一跳到下一天,中间的间隔会与预期不同。
如果业务对「严格等间隔」有要求,比如限流场景要求两次调用至少间隔 N 分钟,就不能依赖 */N。这类需求需要在应用层做节流,或者用 at 命令、systemd timer 的 OnUnitActiveSec= 这类「相对上次执行时间」的调度方式。
组合写法要注意语义
步长可以和其他写法组合,但组合后的语义有时并不直观。
0-30/10 表示 0 到 30 之间每隔 10 取一个,结果是 0、10、20、30。1-59/2 表示从 1 开始每隔 2 取一个,得到 1、3、5、…、59,也就是奇数分钟。这类写法在需要错开任务时很有用。
还有一个容易踩的点:列表里不能用范围简写混搭步长。像 5,*/10 这样的写法在不同实现里行为不一致,有些解析器会报错,有些会把它解释成「5 加上 0、10、20…」。要表达这种组合,最好显式写全:5,10,20,30,40,50。
为什么会有「每次都在整点跑」的错觉
初学时另一个困惑是:为什么每 5 分钟的任务,总觉得它老在整点附近跑?
原因是 */5 的取值里包含了 0 分。整点是大多数日志、报表、监控采样集中的时刻,任务在 0 分执行时,正好与其他系统活动重叠,给人印象更深。而 55 分那次执行,因为紧接着又是下一次,容易被忽略。
如果希望避开整点的资源竞争,可以把步长整体偏移,例如用 2-59/5 让执行时刻落在 2、7、12、…、57 分,避开 0 分。这也是运维上错开多个定时任务的常用手段。
检查清单
/N的起点是字段范围的起点(分钟和秒是 0,日期是 1),不是配置时刻*/5等价于0-59/5,展开为 0、5、10、…、55 这些固定时刻- 范围不能被 N 整除时,跨边界的间隔会变短,
*/7的最后一跳只有 4 分钟 - 需要严格等间隔时不要用
*/N,改用相对上次执行时间的调度方式 - 列表与步长混搭的写法可移植性差,建议显式列举
- 想避开整点竞争,用偏移起点的方式,例如
2-59/5