⏰ 第12课:Cron与定时任务

阶段二:用户与进程第6/6课

📚 课程目标

一、cron概述

1.1 cron工作原理

┌─────────────┐     ┌─────────────┐     ┌──────────────┐
│ crond守护进程 │────▶│  读取配置    │────▶│  执行任务     │
│ (每分钟检查)  │     │  crontab文件 │     │  记录日志     │
└─────────────┘     └─────────────┘     └──────────────┘

1.2 cron相关目录

/etc/crontab          # 系统级定时任务
/etc/cron.d/          # 系统定时任务目录
/etc/cron.daily/      # 每日执行
/etc/cron.hourly/     # 每小时执行
/etc/cron.weekly/     # 每周执行
/etc/cron.monthly/    # 每月执行
/var/spool/cron/      # 用户crontab存储
/var/log/cron         # cron日志
$ ls /etc/cron.d/
certbot
e2scrub_all
sgagenttask
sysstat
yunjing
$ ls /etc/cron.daily/
apport
apt-compat
aptitude
dpkg
logrotate
man-db
sysstat

二、crontab时间表达式

2.1 基本格式

# ┌──────── 分钟 (0-59)
# │ ┌────── 小时 (0-23)
# │ │ ┌──── 日 (1-31)
# │ │ │ ┌── 月 (1-12)
# │ │ │ │ ┌ 星期 (0-7, 0和7都表示周日)
# │ │ │ │ │
  * * * * * command

2.2 特殊符号

符号含义示例
*任意值* * * * * 每分钟
,列举0,30 * * * * 每小时的0分和30分
-范围0 9-17 * * * 9-17点每小时
/步长*/5 * * * * 每5分钟

2.3 常用示例

*/5 * * * * /opt/health-check.sh    # 每5分钟
0 2 * * * /opt/backup-db.sh         # 每天凌晨2点
0 3 * * 1 /opt/clean-logs.sh        # 每周一凌晨3点
0 0 1 * * /opt/monthly-report.sh    # 每月1号0点
0 9 * * 1-5 /opt/daily-check.sh     # 工作日每天9点

2.4 特殊时间字符串

字符串等价于含义
@yearly0 0 1 1 *每年一次
@monthly0 0 1 * *每月一次
@weekly0 0 * * 0每周一次
@daily0 0 * * *每天一次
@hourly0 * * * *每小时一次
@rebootN/A每次启动后执行

三、用户级crontab

$ crontab -l
*/5 * * * * flock -xn /tmp/stargate.lock -c '/usr/local/qcloud/stargate/admin/start.sh > /dev/null 2>&1 &'
crontab -e     # 编辑当前用户的crontab
crontab -l     # 查看当前用户的crontab
crontab -r     # 删除当前用户的crontab
sudo crontab -e -u username    # 编辑其他用户的

四、系统级crontab

# /etc/crontab 多了一个"用户"字段
0 2 * * * root /opt/backup.sh
*/10 * * * * www-data /opt/check.sh

五、systemd Timer

特性cronsystemd Timer
精度1分钟1秒
错过执行跳过可配置追赶
依赖管理
日志需手动自动journal
随机延迟需脚本RandomizedDelaySec
$ systemctl list-timers --all | head -10
NEXT                            LEFT LAST                              PASSED UNIT                           ACTIVATES
Tue 2026-05-19 22:00:00 CST      37s Tue 2026-05-19 21:50:01 CST     9min ago sysstat-collect.timer          sysstat-collect.service
Tue 2026-05-19 22:35:30 CST    36min Tue 2026-05-19 21:37:26 CST    21min ago fwupd-refresh.timer            fwupd-refresh.service
Tue 2026-05-19 22:43:00 CST    43min Tue 2026-05-19 07:16:59 CST      14h ago apt-daily.timer                apt-daily.service
Wed 2026-05-20 00:00:00 CST  2h 0min Tue 2026-05-19 00:00:01 CST            - dpkg-db-backup.timer           dpkg-db-backup.service
Wed 2026-05-20 00:00:00 CST  2h 0min Tue 2026-05-19 00:00:01 CST            - logrotate.timer                logrotate.service
Wed 2026-05-20 00:07:00 CST  2h 7min -                                      - sysstat-summary.timer          sysstat-summary.service
Wed 2026-05-20 01:44:36 CST 3h 45min Tue 2026-05-19 12:02:41 CST       9h ago motd-news.timer                motd-news.service
Wed 2026-05-20 02:20:59 CST 4h 21min Tue 2026-05-19 02:20:59 CST      19h ago update-notifier-download.timer update-notifier-download.service
Wed 2026-05-20 02:30:52 CST 4h 31min Tue 2026-05-19 02:30:52 CST      19h ago systemd-tmpfiles-clean.timer   systemd-tmpfiles-clean.service
# myapp.timer
[Unit]
Description=Run MyApp Backup daily

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

六、at — 一次性定时任务

$ atq
at not installed
at 10:00 AM           # 在指定时间执行
at now + 5 minutes    # 5分钟后
at 3:00 AM tomorrow   # 明天3点
atq                   # 查看待执行
atrm job_number       # 删除任务

七、定时任务最佳实践

📋 Cron任务编写规范

  1. 使用完整路径:cron环境变量极少
  2. 重定向输出:否则cron会将输出邮寄
  3. 加锁防止重复:使用flock
  4. 记录日志:输出重定向到日志文件
  5. 错误处理:使用set -e和trap
  6. 时区注意:cron使用系统时区
# 推荐的crontab写法
0 2 * * * flock -n /tmp/backup.lock /opt/backup.sh >> /var/log/backup.log 2>&1

# 脚本模板
#!/bin/bash
set -euo pipefail
LOG="/var/log/myapp-cron.log"
exec >> "$LOG" 2>&1
echo "$(date): Start"
/opt/myapp/task.sh
echo "$(date): Done"

八、故障排查

❌ cron任务不执行

# 1. 检查crond服务
systemctl status cron

# 2. 查看cron日志
grep CRON /var/log/syslog | tail -10
journalctl -u cron | tail -10

# 3. 检查环境变量
export PATH="/usr/local/bin:/usr/bin:/bin"

# 4. 检查权限
ls -la /opt/backup.sh

# 5. 手动执行脚本测试

九、高级定时任务模式

9.1 分布式锁与防重

# 使用flock防止重复执行
flock -n /tmp/myapp.lock /opt/myapp/task.sh
# -n: 非阻塞,如果获取锁失败则退出

# 使用PID文件
PIDFILE=/tmp/myapp.pid
if [ -f "$PIDFILE" ]; then
    if kill -0 $(cat "$PIDFILE") 2>/dev/null; then
        echo "Already running"
        exit 1
    fi
fi
echo $$ > "$PIDFILE"
# ... 任务逻辑 ...
rm -f "$PIDFILE"

9.2 条件执行

# 仅在工作日执行
0 9 * * 1-5 [ $(date +\%u) -le 5 ] && /opt/work-task.sh

# 仅在月初和月中执行
0 2 1,15 * * /opt/biweekly-task.sh

# 闰年2月29日
0 0 29 2 * /usr/bin/test $(date +\%Y) -eq $(date -d "next year" +\%Y) || /opt/leap-task.sh

9.3 分布式定时任务

多台服务器环境避免同一任务重复执行:

# 方案1:使用数据库锁
mysql -e "SELECT GET_LOCK('cron_task', 0)" | grep -q 1 && /opt/task.sh

# 方案2:使用Redis锁
redis-cli setnx cron:task $(hostname) && /opt/task.sh

# 方案3:指定执行节点
if [ "$(hostname)" = "cron-master" ]; then
    /opt/task.sh
fi

十、练习

📝 创建定时任务

# 1. 创建每5分钟输出时间的任务
(crontab -l 2>/dev/null; echo "*/5 * * * * date >> /tmp/cron-test.log") | crontab -
crontab -l

# 2. 等待几分钟后查看
cat /tmp/cron-test.log

# 3. 清理
crontab -r
rm /tmp/cron-test.log
🏆

成就解锁:时间管理者

✅ 理解cron工作原理

✅ 掌握crontab时间表达式

✅ 学会系统级和用户级定时任务

✅ 了解systemd Timer替代方案

✅ 掌握定时任务调试与最佳实践

🎉 阶段二(用户与进程)完成!下一阶段:网络配置

十一、定时任务监控与报告

#!/bin/bash
# cron-monitor.sh - 定时任务执行监控
# 检查所有定时任务的执行状态

echo "===== Cron任务执行报告 ====="
echo "时间: $(date)"
echo

# 列出所有用户crontab
echo "--- 用户crontab ---"
for user in $(cut -d: -f1 /etc/passwd); do
    crontab=$(sudo crontab -l -u "$user" 2>/dev/null)
    if [ -n "$crontab" ]; then
        echo "用户: $user"
        echo "$crontab"
        echo
    fi
done

# 检查系统crontab
echo "--- /etc/cron.d/ ---"
for file in /etc/cron.d/*; do
    [ -f "$file" ] && echo "文件: $file" && grep -v "^#" "$file" | grep -v "^$"
    echo
done

# 检查cron服务状态
echo "--- Cron服务状态 ---"
systemctl status cron --no-pager 2>/dev/null || systemctl status crond --no-pager

# 最近的cron日志
echo
echo "--- 最近cron执行记录 ---"
journalctl -u cron --since "1 hour ago" --no-pager | tail -10

十二、anacron — 非连续运行系统

anacron专为不持续运行的系统(如笔记本电脑、桌面)设计。如果系统在计划时间关机,anacron会在下次启动时补执行错过的任务。

# /etc/anacrontab
# 周期  延迟(分钟)  任务标识  命令
1       5           cron.daily    run-parts /etc/cron.daily
7       25          cron.weekly   run-parts /etc/cron.weekly
@monthly 45         cron.monthly  run-parts /etc/cron.monthly
服务器通常持续运行,使用cron即可。桌面/笔记本系统使用anacron更合适,它能确保错过的定期任务在系统启动后补执行。

十三、生产环境定时任务管理策略

📋 定时任务设计原则

  1. 幂等性:任务可安全重复执行,不会产生副作用
  2. 防重入:使用flock或PID文件防止并发执行
  3. 超时控制:设置timeout,防止任务无限运行
  4. 告警机制:失败时通知(邮件/webhook)
  5. 日志记录:记录开始/结束时间和关键结果
  6. 版本管理:crontab和脚本纳入Git管理
# 生产级定时任务模板
#!/bin/bash
set -euo pipefail

# 配置
TASK_NAME="database-backup"
LOCK_FILE="/tmp/$TASK_NAME.lock"
LOG_FILE="/var/log/$TASK_NAME.log"
TIMEOUT=3600  # 1小时超时
ALERT_WEBHOOK="https://hooks.slack.com/xxx"

# 日志函数
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') [$TASK_NAME] $*"; }

# 告警函数
alert() {
    log "ALERT: $*"
    curl -s -X POST "$ALERT_WEBHOOK" -d '{"text":"['"$(hostname)"'] '"$*"'"}' > /dev/null 2>&1 || true
}

# 加锁
exec 200>"$LOCK_FILE"
flock -n 200 || { log "Another instance is running"; exit 0; }

# 超时保护
timeout "$TIMEOUT" bash -c '
    # 主任务逻辑
    /opt/backup/db-dump.sh
    /opt/backup/upload-s3.sh
' && log "Task completed successfully" || {
    alert "Task failed with exit code $?"
    exit 1
}