一、什么时候需要定时任务?
读完这篇你会:写出稳定可靠的定时任务,并且知道为什么你的任务没跑。
前置条件:能登录 Linux 服务器,有普通用户权限(部分操作需要 root)。我在 Ubuntu 22.04 和 CentOS 7 上验证过。
二、crontab 基础(90% 场景够用)
2.1 编辑你的定时任务
第一次运行会让你选编辑器,我习惯选 nano 或 vim。
输入一行任务,例如每天凌晨 2 点 30 分执行备份脚本:
30 2 * * * /home/tony/backup.sh
保存退出。查看当前任务:
2.2 时间格式拆解
特殊符号:
举个栗子:
# 每小时的第5分钟执行5 * * * * /path/to/command# 每周一上午9点0 9 * * 1 /path/to/command# 每15分钟*/15 * * * * /path/to/command# 每月1号和15号凌晨3点0 3 1,15 * * /path/to/command
2.3 快捷字符串(省脑子)
我的习惯:能用快捷字符串就用,减少拼写错误。
@daily /opt/scripts/cleanup.sh@reboot /usr/local/bin/start_myapp.sh
三、系统级定时任务(root 或服务)
3.1 /etc/crontab 文件
系统级任务多了一个“用户”字段:
# 格式:分 时 日 月 周 用户 命令0 2 * * * root /usr/bin/backup.sh
编辑系统任务需要 root 权限:sudo vim /etc/crontab。
3.2 /etc/cron.d/ 目录
把每个任务单独放一个文件,方便用包管理分发。例如 /etc/cron.d/mysql-backup:
30 3 * * * mysql_user /opt/scripts/mysql_backup.sh
3.3 预置目录(适合简单脚本)
/etc/cron.hourly//etc/cron.daily//etc/cron.weekly//etc/cron.monthly/
把可执行脚本放进去就行。注意:脚本不能带点号(如 backup.sh 可以,backup.sh.old 会被忽略)。
四、我踩过的三个大坑
坑 1:环境变量不一样
cron 执行时 PATH 非常精简,可能只有 /usr/bin:/bin。你的脚本里如果用了 /usr/local/bin/mysqldump,就会报 command not found。
解决方法:
PATH=/usr/local/bin:/usr/bin:/bin30 2 * * * /home/tony/backup.sh
坑 2:% 号需要转义
cron 中 % 表示换行。如果你在命令里用了日期格式化(比如 date +%Y%m%d),会莫名失败。
错误示例:
0 1 * * * echo `date +%Y%m%d` > /tmp/date.txt # 不行
正确做法:把命令放进脚本,或者在 crontab 里转义:
0 1 * * * echo `date +\%Y\%m\%d` > /tmp/date.txt
老实说,我选择写脚本,不在 crontab 里堆复杂逻辑。
坑 3:输出不处理,磁盘被日志填满
cron 默认会把命令的输出(stdout/stderr)发邮件给用户。如果你没配邮件,这些输出会积在 /var/mail/$USER,久而久之磁盘爆了。
解决:把输出重定向。
# 标准输出和错误都扔到黑洞30 2 * * * /home/tony/backup.sh > /dev/null 2>&1# 或者记到日志文件30 2 * * * /home/tony/backup.sh >> /var/log/backup.log 2>&1
五、验证你的定时任务跑没跑
5.1 查看日志
- Ubuntu/Debian:
grep CRON /var/log/syslog - CentOS/RHEL
你会看到类似这样的行:
Jan 30 02:30:01 hostname CRON[12345]: (tony) CMD (/home/tony/backup.sh)Jan 30 02:30:01 hostname CRON[12344]: (tony) CMDOUT (Backup finished)
5.2 手动跑一次脚本
# 模拟 cron 的最小环境env -i PATH=/usr/bin:/bin /home/tony/backup.sh
如果这样能成功,cron 里大概率也能成功。
六、进阶:systemd timer(cron 的现代替代)
从 2016 年起,很多发行版(比如 CentOS 7+、Ubuntu 16.04+)开始推荐用 systemd timer。它的好处:
6.1 一个简单的 timer 例子
假设你要每天凌晨 3 点运行脚本 /opt/backup.sh。
创建 service 单元/etc/systemd/system/backup.service:
[Unit]Description=My backup script[Service]Type=oneshotExecStart=/opt/backup.sh
创建 timer 单元/etc/systemd/system/backup.timer:
[Unit]Description=Run backup daily at 3am[Timer]OnCalendar=*-*-* 03:00:00Persistent=true # 如果错过,重启后补跑[Install]WantedBy=timers.target
启用并启动:
sudo systemctl daemon-reloadsudo systemctl enable backup.timersudo systemctl start backup.timer
查看 timer 状态:
systemctl list-timers --all
你会看到下一次执行时间。
6.2 彩蛋:随机延迟
如果很多机器同时回调同一个 API,可以加随机延迟:
[Timer]OnCalendar=*-*-* 03:00:00RandomizedDelaySec=3600 # 最多随机延迟1小时
这样每台机器会在 3 点到 4 点之间随机执行,错开高峰。
七、常见问题(真有人问过我)
Q:为什么我写的 crontab 没执行?A:检查三件事:① 确认时间格式正确(用 crontab -l 再看一遍);② 看日志 /var/log/syslog 或 /var/log/cron 有没有执行记录;③ 手动跑脚本看会不会报错。
Q:@reboot 不生效?A:确认你的 cron 服务支持 @reboot(大部分都支持)。另外,@reboot 在系统启动时运行,但不会等待网络就绪。如果你的脚本需要网络,加个 sleep 30。
Q:如何让任务只在工作日的上午 9 点到下午 5 点每 10 分钟执行一次?A:*/10 9-17 * * 1-5 /path/to/command。注意周末(6、7)排除。
Q:systemd timer 和 cron 能混用吗?A:能。但别用同一个脚本同时注册两套,否则会重复执行。
八、验证你学会了
自己动手:写一个定时任务,每天下午 3 点 15 分记录系统负载到 /tmp/load.log。
参考答案:
crontab -e# 添加这一行15 15 * * * uptime >> /tmp/load.log 2>&1
等第二天下午,查看 /tmp/load.log 是否有内容。或者把时间改成几分钟后测试(比如当前 14:50,写 51 14 * * * ...)。
定时任务看着简单,但生产环境里翻车最多的就是它——因为你写的时候没有真的跑。我建议每次新增任务后,先调成几分钟后执行,确认没问题再改回目标时间。
你遇到过哪些定时任务的诡异问题?来评论区说说,我帮你分析。如果觉得有用,分享给那个总在凌晨被 oncall 叫醒的兄弟。