从 cron 到 systemd timer:一次定时任务迁移的完整实践
从 cron 到 systemd timer:一次定时任务迁移的完整实践
一、为什么要把 cron 换掉
cron 简单可靠,几乎每台 Linux 上都有。但它在近几年暴露了三个明显的短板:
- 没有日志归集:任务输出只进 MAILTO,容器里常常直接丢失,排障全靠
2>&1 >> file手工重定向。 - 没有依赖与时序控制:上一条任务还没跑完,下一条又起来了,只能靠 flock + 手写锁。
- 不感知系统状态:错过执行时间(机器关机、休眠)后不会补跑,也没有"延迟执行""随机抖动"这类能力。
systemd timer 把这些能力交给了 systemd 本身:统一日志走 journald、After=/Requires= 管依赖、Persistent=true 支持补跑、RandomizedDelaySec= 做抖动,写起来也不复杂。
二、把一行 crontab 拆成两个 unit
假设原来有这一行:
# 每天 06:00 备份数据库
0 6 * * * /usr/local/bin/backup-db.sh >> /var/log/backup.log 2>&1
迁移后拆成 service 和 timer 两个文件。
/etc/systemd/system/backup-db.service:
[Unit]
Description=Backup MySQL database
After=network-online.target mysql.service
Wants=network-online.target
[Service]
Type=oneshot
User=backup
WorkingDirectory=/opt/backup
ExecStart=/usr/local/bin/backup-db.sh
# 失败重试:5 分钟内最多 3 次
Restart=on-failure
RestartSec=30s
StartLimitIntervalSec=300
StartLimitBurst=3
/etc/systemd/system/backup-db.timer:
[Unit]
Description=Run backup-db.service every day at 06:00
[Timer]
OnCalendar=*-*-* 06:00:00
Persistent=true
RandomizedDelaySec=300
AccuracySec=1s
Unit=backup-db.service
[Install]
WantedBy=timers.target
启用:
sudo systemctl daemon-reload
sudo systemctl enable --now backup-db.timer
systemctl list-timers --all | grep backup
三、几个关键参数的取舍
Persistent=true 解决"错过执行"问题:系统关机错过了 06:00,下次开机后 systemd 会立刻补跑一次。对备份这类必须每天有一次的任务非常关键;对"只在整点窗口内才有意义"的任务(比如 09:30 抢券脚本)就要谨慎,最好在脚本内部再判断一次时间窗口。
RandomizedDelaySec 是为了避免大量机器同一秒打向同一台服务器。cron 时代靠 sleep $((RANDOM % 300)),现在一行配置就够了。
AccuracySec 默认是 1min,systemd 为了省电会把多个 timer 合并唤醒,触发时间可能晚最多一分钟。要求准点就显式写 AccuracySec=1s。
OnCalendar 与 OnUnitActiveSec 的区别:前者是绝对时间(每天 06:00),后者是相对时间(上次激活后 30 分钟),等价于 cron 做不到的 */30 漂移执行。
四、日志:从重定向到 journalctl
service 里不需要再写 >> /var/log/xxx.log,直接:
journalctl -u backup-db.service -n 100 --no-pager
journalctl -u backup-db.service --since "24 hours ago"
如果确实需要落文件,加一行:
[Service]
StandardOutput=append:/var/log/backup.log
StandardError=append:/var/log/backup.log
既保留了文件,也留了一份在 journald 里可以按时间检索。
五、用 systemd-analyze 验证日历语法
OnCalendar 的语法有坑,写错了不会报错,只会永远不触发。用:
systemd-analyze calendar "*-*-* 06:00:00"
systemd-analyze calendar --iterations=5 "Mon..Fri 09:30"
它会直接打印下一次和之后的触发时间,是最省事的自检方式。
六、迁移清单
- 逐条把 crontab 拆成
xxx.service+xxx.timer; - 需要补跑的任务加
Persistent=true; - 批量任务加
RandomizedDelaySec,避免惊群; - 原脚本里的
flock可以保留,也可以换成Type=oneshot+ExecStartPre做前置检查; - 用
systemctl list-timers和journalctl -u建立新的观测习惯。
七、小结
systemd timer 不是银弹,它依赖 systemd,容器化环境里仍然可能优先用 cron 或调度平台。但在常规 Linux 服务器上,它把日志、依赖、补跑、抖动四件事一次性解决掉,配置成本也不高。一次迁移之后,排障时的"这个任务到底跑没跑、输出在哪"这类问题基本就消失了。
- 点赞
- 收藏
- 关注作者
评论(0)