Skip to content

计划任务

定时执行脚本的机制有几种:cron(传统定时任务)、anacron(补充错过任务的场景)、at(一次性延迟执行)、systemd timer(systemd 自带的定时器)。

一、crontab

cron 的守护进程 crond 按预设的时间表达式扫描任务,到时间就用对应用户身份执行命令。实际出问题的往往不是时间表达式写错,而是执行环境与手动执行时不同。

查看当前用户的定时任务:

bash
crontab -l

编辑:

bash
crontab -e

crontab 格式,每行一个任务:

分 时 日 月 周 命令

五个时间字段:

字段取值范围说明
0-59
0-23
1-31
1-12
0-70 和 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:30

cron 里命令写绝对路径是个好习惯。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/anacrontab

anacron 适合不是 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.target

timer 常用字段:

字段含义
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 的状态透明度和日志可追踪性更有价值。