4. cron 高级用法:掌握时间的艺术

各位同学好,上一章我们搞定了 cron 的基本表达式。但说实话,生产环境里光会写「每天凌晨两点跑一次」是远远不够的。我见过不少同事,写个每分钟执行的定时任务都翻车——不是脚本没跑起来,就是把系统搞崩了。这一章,我们来聊聊那些真正提升运维效率的高级用法。

4.1 步长值:别再写一长串重复的 cron 行

什么是步长值?说白了就是在字段后面加 /N,表示每隔 N 个单位执行一次。

# 每隔 5 分钟执行一次
*/5 * * * * /usr/local/bin/check_disk.sh

# 每隔 2 小时执行一次
0 */2 * * * /usr/local/bin/check_nginx.sh

# 每隔 10 分钟,但只在工作日的上午 9 点到下午 6 点执行
*/10 9-18 * * 1-5 /usr/local/bin/check_service.sh

我在项目中遇到过一种情况:有人为了「每 15 分钟执行一次」,写了 4 行 cron,每行指定不同的分钟(0,15,30,45)。其实一行 */15 就搞定了。你想想看,这多省事。

我的习惯:步长值的起点始终从 0 开始。比如 */5 会在 0,5,10,15... 分钟触发。如果你想让它在第 2 分钟开始每 5 分钟执行一次,写 2-59/5 就行。

4.2 逗号与列表:精准指定离散时间点

有些场景不需要「每隔 X 分钟」,而是要在几个特定的时间点执行。比如业务高峰期(10:00、14:00、16:00)检测一下数据库连接数。

# 每天上午 10 点、下午 2 点、下午 4 点执行
0 10,14,16 * * * /usr/local/bin/check_mysql.sh

# 每周一、周三、周五的凌晨 3 点执行
0 3 * * 1,3,5 /usr/local/bin/clean_logs.sh

# 每个月的 1 号和 15 号执行
0 0 1,15 * * /usr/local/bin/report_monthly.sh

逗号可以和步长值混用。比如 0,30 */2 * * * 表示每个偶数小时的 0 分和 30 分执行。嗯,这里要注意:逗号是「或」的关系,不是区间。

4.3 日期与星期组合:容易踩坑的地方

这是 cron 里最让人迷惑的点了。五个字段里,日和星期是「或」的关系,不是「与」。

# 你以为:每个月的 1 号且是周二才执行
# 实际上:每个月的 1 号 或者 每个周二都执行
0 0 1 * 2 /usr/local/bin/special_job.sh

我曾经吃过这个亏。有次写了个定时备份脚本,想「每月 1 号 和 每周日」都执行,结果发现每周日也执行了——其实就是更频繁了。要想实现「并且」的逻辑,必须在脚本内部判断。

避坑指南:不要在 cron 里依赖「日 and 星期」的逻辑。如果你真的需要这种组合,建议在脚本开头加一行 if [ $(date +%d) -ne 1 ] && [ $(date +%u) -ne 7 ]; then exit; fi,自己控制条件。

4.4 系统简写:@reboot、@daily、@hourly 等

我刚开始用 Linux 时,看到 @reboot 觉得很神奇。其实这些简写是系统预定义的别名,内部会解析成标准 cron 表达式。

简写 等价表达式 执行时机 我常用的场景
@reboot 无 系统启动后执行一次 启动自检、服务预热、清理临时文件
@yearly 或 @annually 0 0 1 1 * 每年 1 月 1 日 00:00 年度归档、日志轮转
@monthly 0 0 1 * * 每月 1 日 00:00 月度报告生成、清理过期备份
@weekly 0 0 * * 0 每周日 00:00 全量备份、系统更新检查
@daily 或 @midnight 0 0 * * * 每天 00:00 日常巡检、日志切割
@hourly 0 * * * * 每小时整点 服务健康检查、流量监控

我个人习惯:在 crontab 里尽量写标准表达式,而不是简写。原因有两个:一是简写容易让人忘记它具体在什么时间触发;二是跨平台时有些系统的简写支持不完整。但 @reboot 例外——这个太实用了,而且几乎所有类 Unix 系统都支持。

4.5 @reboot 的实战技巧

说到 @reboot,我记得有次接手一个老系统,每次重启后都得手动启动好几个服务。后来我在 crontab 里加了几行:

# 系统启动后 30 秒,启动核心服务
@reboot sleep 30 && /etc/init.d/nginx start

# 启动后 60 秒,自检磁盘空间
@reboot sleep 60 && /usr/local/bin/check_disk.sh

# 启动后 120 秒,清理临时文件
@reboot sleep 120 && /usr/local/bin/clean_temp.sh

为什么要加 sleep?因为系统刚启动时,依赖的服务可能还没完全就绪。你想想看,nginx 启动时发现网络接口还没起来,那不就尴尬了吗?所以用 sleep 错峰启动,稳得很。

注意:@reboot 的执行用户是谁,脚本就跑在谁的上下文里。如果你用 root 用户的 crontab 写 @reboot,那脚本就是 root 权限。这个一定要小心,别把系统搞坏了。

4.6 知识结构一览

我把这一章的核心逻辑画了张图,方便你快速回顾:

cron 高级用法知识结构 cron 高级用法 步长值 */N 逗号/列表 , 日期范围组合 系统简写 @xxx @reboot 实战技巧 每隔 N 单位执行一次 选择特定时间点 日和星期是「或」关系 @reboot 加 sleep 错峰

4.7 综合实战案例:一套完整的巡检 cron 配置

最后,我分享一个我自己线上正在用的 crontab 片段,它综合了本章所有知识点:

# ──── 高频检查(每 5 分钟)────────────
*/5 * * * * /opt/scripts/check_cpu_mem.sh
*/5 * * * * /opt/scripts/check_disk_io.sh

# ──── 中频检查(每小时整点和半点)────
0,30 * * * * /opt/scripts/check_nginx_conn.sh

# ──── 定时清理(每天凌晨 3 点)──────
0 3 * * * /opt/scripts/clean_logs.sh

# ──── 业务峰值期加强监控(上午 10、11 点,下午 2、3、4 点)
0 10,11,14,15,16 * * * /opt/scripts/check_api_perf.sh

# ──── 特殊日期:每月 1 号 + 每周日做全量健康检查
0 2 * * 0 /opt/scripts/full_health_check.sh
0 2 1 * * /opt/scripts/full_health_check.sh

# ──── 系统重启后自检(错峰启动)
@reboot sleep 30 && /opt/scripts/start_self_check.sh
@reboot sleep 90 && /opt/scripts/warmup_cache.sh

嗯,这里我特意把「每月 1 号 和 每周日」拆成了两行,而不是写在同一行。因为前面说过,日和星期是「或」关系,写一起反而每天都可能触发。我之前也踩过这个坑,后来就改成这样——虽然多一行,但逻辑清晰,不会出幺蛾子。

一个小建议:每行 cron 后面都加个注释,写清楚这个任务的作用。你想想看,三个月后你再回来看这个文件,如果没有注释,你可能都忘了当时为什么要设这个时间。我吃过这个亏,现在每条都写注释。