定时任务调度实战

系统讲解 cron 的字段语法与 crontab 管理、环境与路径陷阱、日志与错误处理,at 与 Anacron 的补充场景,systemd timer 的现代替代,以及任务重叠防护的完整实践。

1. cron 基础语法

一句话总结: cron 一行五字段(分 时 日 月 周)+ 命令,分与周同时设置时是 OR 语义,这是最常见的语法误读。

cron 表每行一个任务:分 时 日 月 周 命令。五个时间字段各有取值范围。

# 每分钟
* * * * * echo "tick" >> /tmp/cron.log

# 每天 2:30
30 2 * * * /backup/backup.sh

# 每周一凌晨 3:00
0 3 * * 1 /backup/weekly.sh

# 每月 1 号 4:00
0 4 1 * * /report/monthly.sh

1.1 字段取值与特殊字符

字段范围特殊字符
分0-59* , - /
时0-23* , - /
日1-31* , - ?
月1-12* , - /
周0-7* , -(0 与 7 都指周日)
# 每 15 分钟
*/15 * * * * cmd

# 工作日上午 9 点到 11 点
0 9-11 * * 1-5 cmd

# 每月 1、15 号
0 2 1,15 * * cmd

一句话总结: */n 步长、a,b 枚举、a-b 范围;注意「日 OR 周」——两者都非 * 时任一匹配即触发。

1.2 日与周的 OR 语义

# 本意「每月 1 号 或 每周一」,实为 OR:
0 3 1 * 1 cmd    # 每月 1 号 与 每周一 都会执行

# 要「每月 1 号且是周一」需在命令里判断
0 3 1 * * test "$(date +%u)" = 1 && cmd

2. crontab 管理

一句话总结: crontab -e 编辑个人表、-l 查看、-r 删除;系统任务放 /etc/crontab 或 /etc/cron.d/,脚本放 cron.daily 等目录更规范。

# 查看当前用户任务
crontab -l

# 编辑
crontab -e

# 从文件安装(适合脚本化管理)
crontab install_cron.txt

# 删除全部
crontab -r

2.1 系统级 cron

# /etc/cron.d/ 下带用户字段的任务
echo '0 2 * * * root /opt/cleanup.sh' > /etc/cron.d/cleanup
chmod 644 /etc/cron.d/cleanup

# 按小时/天/周分桶
ls /etc/cron.hourly /etc/cron.daily /etc/cron.weekly

# 脚本放进桶目录即可被 anacron/cron 调度
cp backup.sh /etc/cron.daily/backup
chmod +x /etc/cron.daily/backup

一句话总结: 个人任务用 crontab -e,系统级任务用 /etc/cron.d/ 或分桶目录,后者由 run-parts 统一执行,管理更清晰。

2.2 脚本化管理 crontab

#!/usr/bin/env bash
set -euo pipefail

# 生成 crontab 内容并安装
{
  echo "# 备份任务"
  echo "30 2 * * * /opt/backup.sh"
  echo "0 3 * * 1 /opt/weekly.sh"
} | crontab -

# 校验当前配置
crontab -l | grep -v '^#' | grep -v '^$'

3. 环境与路径陷阱

一句话总结: cron 环境只有最小 PATH,HOME 也不一定对;任务里用绝对路径、显式 source 环境、日志输出到文件,否则脚本「手动能跑、定时不跑」。

# 常见故障:PATH 缺失
# 0 2 * * * docker ps        # 找不到 docker
# 0 2 * * * /usr/bin/docker ps   # 绝对路径可用

# 在 crontab 顶部设置 PATH
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# 脚本开头 source 环境
# source /etc/profile
# source ~/.bashrc

3.1 输出重定向与日志

# 全部输出进日志
30 2 * * * /opt/backup.sh >> /var/log/backup.log 2>&1

# 错误单独记录
30 2 * * * /opt/backup.sh >> /var/log/backup.log 2>> /var/log/backup.err

一句话总结: cron 任务默认无终端输出,必须手动 >> log 2>&1;日志挂载到独立文件,排查才有一手证据。

3.2 时间与时区

# 检查 cron 使用的时区
date
grep CRON /var/log/syslog | head

# 统一时区(对容器很重要)
timedatectl set-timezone Asia/Shanghai

# 脚本内显式指定 TZ 避免歧义
TZ=Asia/Shanghai /opt/report.sh

4. 日志与错误处理

一句话总结: cron 自身日志在 /var/log/syslog 或 journald,任务内先 set -euo pipefail、失败发告警、最后 cron 邮件或外部通知兜底。

# 查看 cron 触发记录
grep CRON /var/log/syslog | tail -20

# 用 systemd 的 cron 服务日志
journalctl -u cron --since today

4.1 任务内错误处理模板

#!/usr/bin/env bash
set -euo pipefail

LOG=/var/log/myjob.log
exec >> "$LOG" 2>&1

echo "===== 开始 $(date) ====="
if /opt/do_work; then
  echo "成功"
else
  echo "失败 exit=$?"
  # 告警:邮件或 webhook
  curl -fsS -X POST https://alerts.example.com/notify \
    -d "job=myjob&status=fail"
  exit 1
fi

一句话总结: 定时任务脚本三件套:set -euo pipefail、输出全部落日志、失败调外部告警——让机器自己报错而不是人肉盯日志。

4.2 命令退出码与 MAILTO

# crontab 里设置收件人(需本地 MTA)
MAILTO=ops@example.com
0 2 * * * /opt/backup.sh

# 只对失败发邮件:命令非零退出才发
0 2 * * * /opt/backup.sh >/dev/null 2>&1 || mail -s 'backup failed' ops@example.com

5. at 一次性任务与 Anacron

一句话总结: at 处理「某时刻跑一次」,Anacron 处理「错过时间也要补跑」,它们是 cron 之外的两块拼图。

# at:明早 3 点执行一次
echo '/opt/cleanup.sh' | at 03:00

# 查看队列与删除
atq
atrm 5

# 表达式形式
at now + 5 minutes
at 2pm tomorrow

5.1 Anacron 补跑错过的任务

cron 不保证笔记本/关机的任务补跑,Anacron 记住上次执行时间,开机后补跑。

# /etc/anacrontab 格式:周期 延迟 任务名 命令
# 7 15 backup.daily /etc/cron.daily/backup

# 手动触发补跑
sudo anacron -f

# 查看上次运行记录
sudo anacron -d -n

一句话总结: 笔记本或不常开的机器用 Anacron 按「距上次执行的间隔」补跑,比 cron 的固定时刻更贴合。

5.2 atd 服务与权限

# 确保 atd 运行
systemctl enable --now atd
systemctl status atd

# 黑名单/白名单文件
ls /etc/at.allow /etc/at.deny

6. systemd timer

一句话总结: systemd timer 用 .timer + .service 两个单元声明定时任务,支持 OnCalendar 日历表达式与 Persistent 补跑、RandomizedDelaySec 抖动,是现代 cron 的替代。

# /etc/systemd/system/backup.service
# [Unit]
# Description=Backup job
# [Service]
# Type=oneshot
# ExecStart=/opt/backup.sh

# /etc/systemd/system/backup.timer
# [Unit]
# Description=Run backup daily
# [Timer]
# OnCalendar=daily
# Persistent=true
# RandomizedDelaySec=300
# [Install]
# WantedBy=timers.target

systemctl enable --now backup.timer
systemctl list-timers --all

6.1 OnCalendar 表达式

写法含义
OnCalendar=daily每天 00:00
OnCalendar=*-*-* 02:30:00每天 2:30
OnCalendar=Mon..Fri 09:00工作日 9 点
OnCalendar=*-*-1..7 04:00每月前 7 天 4 点
# 校验日历表达式
systemd-analyze calendar 'Mon..Fri 09:00'

# 最近 5 次触发时间
systemd-analyze calendar --iterations=5 'daily'

一句话总结: systemd-analyze calendar 校验表达式、--iterations 看未来触发,写错前先算一遍,避免「静默不触发」。

6.2 timer 相比 cron 的优势

# 错过补跑、随机抖动、日志统一、依赖管理
OnCalendar=daily
Persistent=true          # 上次没跑开机补跑
RandomizedDelaySec=600   # 随机延迟防风暴

# 任务依赖与超时(service 单元)
# [Service]
# ExecStart=/opt/backup.sh
# TimeoutStartSec=30min

7. 任务重叠防护

一句话总结: 长任务未结束又触发,用 flock 锁文件或 PID 文件防重入;日志按日期切分,避免单文件无限膨胀。

# flock 防重入(推荐)
#!/usr/bin/env bash
exec 9>/var/lock/backup.lock
flock -n 9 || { echo "上次任务还在跑"; exit 1; }

# 任务主体...

7.1 PID 文件方案

#!/usr/bin/env bash
set -euo pipefail

PIDFILE=/var/run/myjob.pid
if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
  echo "任务已在运行,退出"; exit 1
fi
echo $$ > "$PIDFILE"
trap 'rm -f "$PIDFILE"' EXIT

# 任务主体
sleep 60

一句话总结: flock 锁文件最稳(进程崩溃自动释放),PID 文件需配合 kill -0 与 trap 清理;两者二选一即可。

7.2 日志切分与保留

# 按天滚动日志(任务内)
LOG=/var/log/myjob_$(date +%F).log
exec >> "$LOG" 2>&1

# 定期清理旧日志
find /var/log -name 'myjob_*.log' -mtime +30 -delete

# logrotate 接管
# /etc/logrotate.d/myjob
# /var/log/myjob_*.log {
#     daily
#     rotate 30
#     missingok
# }

8. 总结

环节要点
cron 语法分时日月周五字段,*/n a-b a,b,日周 OR 语义
crontab 管理-e 编辑、-l 查看、文件安装;系统任务用 /etc/cron.d/
环境陷阱PATH/HOME 最小化,绝对路径 + source 环境 + 重定向日志
错误处理set -euo pipefail、输出落日志、失败外部告警
at/Anacronat 一次性、Anacron 补跑错过的任务
systemd timerOnCalendar + Persistent + RandomizedDelaySec 替代 cron
重叠防护flock 锁文件防重入、日志按日期滚动切分
校验systemd-analyze calendar、crontab -l、journalctl -u cron

定时任务是运维自动化的"节拍器":cron 管固定时刻、Anacron 管补跑、systemd timer 管现代声明。脚本写好错误处理与重叠防护,定时任务才能真正"无人值守"。下一步进入网络请求与诊断,用 curl 把脚本与外部服务连接起来。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「shell」更多文章

  1. 脚本性能优化实战
  2. 网络请求与诊断实战
  3. SSH 远程自动化实战