Appearance
计划任务
定时执行脚本的机制有几种:cron(传统定时任务)、anacron(补充错过任务的场景)、at(一次性延迟执行)、systemd timer(systemd 自带的定时器)。
一、crontab
cron 的守护进程 crond 按预设的时间表达式扫描任务,到时间就用对应用户身份执行命令。实际出问题的往往不是时间表达式写错,而是执行环境与手动执行时不同。
查看当前用户的定时任务:
bash
crontab -l编辑:
bash
crontab -ecrontab 格式,每行一个任务:
分 时 日 月 周 命令五个时间字段:
| 字段 | 取值范围 | 说明 |
|---|---|---|
| 分 | 0-59 | |
| 时 | 0-23 | |
| 日 | 1-31 | |
| 月 | 1-12 | |
| 周 | 0-7 | 0 和 7 都表示周日 |
时间表达式写法:
| 写法 | 含义 |
|---|---|
* | 每个值都匹配 |
*/5 | 每 5 个单位 |
1,15,30 | 指定的多个值 |
1-5 | 连续范围 |
1-10/2 | 范围内按步长执行(1,3,5,7,9) |
常见任务示例:
cron
*/5 * * * * /opt/check.sh >>/var/log/check.log 2>&1
0 2 * * * /opt/backup.sh >>/var/log/backup.log 2>&1
30 1 * * 1 /opt/report.sh # 每周一凌晨 1:30cron 里命令写绝对路径是个好习惯。cron 运行时的 PATH 环境变量很短(通常是 /usr/bin:/bin),登录 Shell 里能直接跑的命令放到 cron 里可能因为找不到可执行文件而失败。
二、系统级 cron
除了每个用户自己的 crontab,还有系统级别的定时任务入口:
| 路径 | 用途 |
|---|---|
/etc/crontab | 系统 crontab,格式比用户 crontab 多一个用户字段 |
/etc/cron.d/ | 独立的定时任务文件,格式同 /etc/crontab |
/etc/cron.hourly/ | 每小时执行一次,由 /etc/cron.d/0hourly 触发 |
/etc/cron.daily/ | 每天执行一次 |
/etc/cron.weekly/ | 每周执行一次 |
/etc/cron.monthly/ | 每月执行一次 |
系统级 crontab 多一个用户字段:
cron
*/5 * * * * root /opt/check.sh系统任务放在 /etc/cron.d/服务名 比散落在 root 用户的 crontab -e 里更好维护和交接。
三、cron 的执行环境
cron 运行时的环境变量和交互式 Shell 差别很大。它不会自动加载 ~/.bashrc、~/.bash_profile 等交互式配置文件。
可以在 crontab 文件顶部声明环境变量:
cron
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/5 * * * * /opt/check.sh >>/var/log/check.log 2>&1更稳妥的做法是在脚本内部显式设置所需的环境变量和路径。Java 的 JAVA_HOME、Python 虚拟环境的路径、Node.js 的 nvm 路径、数据库客户端的库路径,都需要在脚本里写清楚。
cron 执行时的工作目录是执行用户的家目录,不是脚本所在目录。脚本里如果用了相对路径,会以家目录为基准。所以定时任务脚本开头通常会有:
bash
#!/bin/bash
cd /opt/myapp || exit 1 # 切换到脚本所在目录,失败则退出四、避免重复执行
如果上一次任务还没跑完,下一个周期又来了,可能导致多个实例同时操作同一份数据。用 flock 加文件锁:
cron
*/5 * * * * flock -n /tmp/check.lock /opt/check.sh >>/var/log/check.log 2>&1-n 表示拿不到锁就直接退出(非阻塞)。备份、数据同步、批量变更这类不适合多实例并发跑的任务,加锁是标准做法。
五、anacron
anacron 解决的是 cron 的一个盲区:任务应该在特定时间执行,但机器当时关机了,错过了怎么办。
bash
cat /etc/anacrontabanacron 适合不是 24 小时开机的桌面机、测试机、边缘节点。服务器通常有常开属性,这个场景差异化不大。
六、at 一次性延迟任务
at 用于在未来某个时间点执行一次性任务。
bash
yum install at -y
apt install at -y
systemctl enable --now atd创建任务:
bash
at now + 10 minutes交互式输入要执行的命令,按 Ctrl+d 结束输入。
管理任务:
bash
atq # 查看待执行的任务队列
atrm <job-id> # 取消指定任务at 适合"10 分钟后回滚""今晚零点关压测""一小时后手动切换回来"这类临时操作。涉及重要变更时,除了 at 任务本身,日志和回滚命令也要写清楚,单靠记忆不可靠。
七、systemd timer
systemd timer 相比 cron 的优势是:状态可查、日志可集中追踪、上次执行结果可见、可以和 service 的生命周期联用。
timer 需要两个文件配合:
| 文件 | 作用 |
|---|---|
xxx.service | 实际要执行的任务(Type=oneshot) |
xxx.timer | 定义触发 service 的时间和策略 |
一个定时巡检的 service:
ini
# /etc/systemd/system/check.service
[Unit]
Description=Run check script
[Service]
Type=oneshot
ExecStart=/opt/check.sh对应的 timer:
ini
# /etc/systemd/system/check.timer
[Unit]
Description=Run check script every 5 minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Unit=check.service
[Install]
WantedBy=timers.targettimer 常用字段:
| 字段 | 含义 |
|---|---|
OnBootSec | 开机后等待多久第一次执行 |
OnUnitActiveSec | 上次 service 激活后再等多久执行 |
OnCalendar | 按日历时间执行(类似 cron 表达式) |
Persistent | 如果错过了(如当时在关机),开机后是否补跑一次 |
Unit | 此 timer 触发哪个 service |
每天凌晨 2 点的备份任务改用 timer 写法:
ini
# /etc/systemd/system/backup.timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
Unit=backup.service
[Install]
WantedBy=timers.target启用和管理:
bash
systemctl daemon-reload
systemctl enable --now check.timer
systemctl list-timers --all # 查看所有 timer 的上次和下次触发时间
journalctl -u check.service -n 100 --no-pager # 看任务输出排查 timer 任务失败的常用命令:
| 命令 | 作用 |
|---|---|
systemctl status xxx.timer | 看 timer 是否启用、下次触发时间 |
systemctl status xxx.service | 看上次任务执行是否成功 |
systemctl list-timers --all | 全景视图,上次和下次执行一目了然 |
journalctl -u xxx.service | 看任务的标准输出和错误 |
cron 简单直接够用,但在任务多了之后,timer 的状态透明度和日志可追踪性更有价值。