定时任务这东西,写对了没人记得,写错了一定上事故复盘会。
下面这些坑我基本都在生产环境见过:有的是任务没跑,有的是跑了三遍,有的是半年后磁盘告警。
一、先把格式记牢
标准 Linux crontab 是 5 段,Spring / Quartz 是 6 段(多了秒):
# Linux crontab:5 段
* * * * * command
│ │ │ │ │
│ │ │ │ └── 星期 (0-7,0 和 7 都是周日)
│ │ │ └───── 月 (1-12)
│ │ └──────── 日 (1-31)
│ └─────────── 小时 (0-23)
└────────────── 分钟 (0-59)
# Spring @Scheduled:6 段(首位是秒)
0 0 2 * * *
最容易犯的错:从 Spring 那边抄一个 0 */5 * * * * 到 crontab,直接报「bad minute」——多了一段。
常用特殊字符:
| 字符 | 含义 | 示例 |
|---|---|---|
* |
任意值 | * * * * * 每分钟 |
, |
列表 | 1,15 1 号和 15 号 |
- |
范围 | 9-18 9 点到 18 点 |
/ |
步长 | */10 每 10 分钟 |
二、8 个能直接抄的例子
0 2 * * * # 每天凌晨 2 点
30 3 * * 1 # 每周一 3:30
*/10 * * * * # 每 10 分钟
0 */2 * * * # 每 2 小时整点
0 9-18 * * 1-5 # 工作日 9-18 点,每小时整点
0 0 1 * * # 每月 1 号 0 点
0 0 1,15 * * # 每月 1 号和 15 号
0 4 1 * * # 每月 1 号 4 点(月度报表)
看着简单,坑在下面。
三、坑 1:5 * * * * 不是「每 5 分钟」
这是我见过最多的错误:
| 写法 | 实际含义 |
|---|---|
*/5 * * * * |
每 5 分钟(0,5,10…55)✅ |
5 * * * * |
每小时的第 5 分钟(1:05、2:05…)❌ |
0/5 * * * * |
从 0 开始每 5 分钟,等价于 */5 |
想「每 5 分钟」却写成 5 * * * *,任务一小时才跑一次,数据延迟一整天才会被发现。
四、坑 2:日和星期同时指定,是「或」不是「且」
0 0 13 * 5
新手以为这是「13 号且是周五」。错了——crontab 的规则是:日 和 星期 两个字段只要有一个匹配就执行。所以这个表达式的意思是「每月 13 号 或 每个周五」都会跑。
要精确限定,就只留一个字段,另一个写 *;或者在执行脚本里自己加日期判断。
五、坑 3:cron 的环境里几乎没有变量
cron 执行时 PATH 通常只有 /usr/bin:/bin。所以:
# 错:node / java / python3 常常找不到,任务静默失败
* * * * * node /opt/app/task.js
# 对:写绝对路径
* * * * * /usr/local/bin/node /opt/app/task.js
# 或者:在 crontab 文件顶部声明 PATH
PATH=/usr/local/bin:/usr/bin:/bin
* * * * * node /opt/app/task.js
调试技巧:在 shell 里用 env -i /bin/bash -c '你的命令' 复现 cron 的空环境,能立刻看出缺什么。
同理,JAVA_HOME、LANG(中文文件名乱码)、TZ(时区,cron 默认用系统时区,容器里常是 UTC)都要显式设置。
六、坑 4:不重定向输出,半年后磁盘告警
cron 的标准输出会发邮件给任务所属用户。邮件服务没配,就堆在 /var/spool/mail/ 里,日积月累能把根分区撑满。
# 正确写法:显式重定向 + 日志切割
*/5 * * * * /opt/app/sync.sh >> /var/log/sync.log 2>&1
配合 logrotate:
/var/log/sync.log {
daily
rotate 7
compress
missingok
notifempty
}
调试期可以临时保留输出,正式任务一定要落文件或丢弃(>/dev/null 2>&1)。
七、坑 5:任务重叠执行
任务要跑 10 分钟,但每 5 分钟触发一次 —— 就会有 2 个实例并发跑。数据同步类任务重叠跑,轻则重复写,重则死锁。
用 flock 加文件锁,一行解决:
*/5 * * * * flock -n /tmp/sync.lock -c '/opt/app/sync.sh >> /var/log/sync.log 2>&1'
-n 表示拿不到锁就直接退出(不等待),不会堆积进程。想等待就去掉 -n,改成 -w 60(最多等 60 秒)。
八、上线前跑一遍检查单
crontab -l确认写进去了,注意用户(root 和 www 的任务完全隔离,部署脚本里别用错-u)- 命令手动跑一次,确认退出码是 0
- 看日志确认真被触发:CentOS/
/var/log/cron,Ubuntu/journalctl -u cron - 涉及写文件的,确认运行用户有权限(这个坑经常在备份任务上出现)
- 改完先设成一个最近的时间点验证(比如下一分钟),别等到凌晨 2 点才发现没跑
- 容器里跑 cron 的,确认 cron 进程是 PID 1 或由 supervisor 托管,否则容器起来后 cron 根本没在跑
九、写完先验证一遍
改完 crontab 别直接上线,把表达式丢进解析器看人类可读的描述和接下来几次执行时间,确认和自己想的一致:
👉 Cron 表达式在线解析 / 生成(输入表达式直接出下次执行时间,也支持反向生成)
十、速查表
crontab -l # 查看当前用户的任务
crontab -e # 编辑
crontab -r # 删除(慎用,无确认)
crontab -u www -l # 查看指定用户的任务
cat /etc/crontab # 系统级任务(多一个「用户」字段)
ls /etc/cron.d/ # 系统级任务目录
系统级 /etc/crontab 是 6 段(分钟 小时 日 月 星期 用户 命令),和 crontab -e 的 5 段不一样,别混着抄。
相关工具:Cron 表达式解析|Linux 命令速查|Docker 命令速查
全部纯前端实现,输入的内容不上传服务器。