cron 用了二十多年但有几个老问题:失败没有 retry、错过的任务(机器关机)
不补跑、日志要自己重定向、表达式只到分钟、找不到任务跑慢了的根因。
systemd timer 把这些一站式解决。
一个完整例子
需求:每小时把 PostgreSQL 备份压缩传到远端,失败重试 3 次。
service 单元
# /etc/systemd/system/pgdump.service
[Unit]
Description=Hourly pg_dump + ship to backup host
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=postgres
Group=postgres
# 失败重试:5s, 15s, 45s 三次
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=120
StartLimitBurst=4
# 运行约束
Nice=10
IOSchedulingClass=idle
TimeoutStartSec=30m
# 命令本身
ExecStart=/usr/local/sbin/pgdump-ship.sh
# 资源限制(避免 OOM 把别的进程踩死)
MemoryMax=2G
TasksMax=64
Restart=on-failure 是关键 —— 脚本非零退出就触发重启。StartLimitBurst=4
意味着 120s 内最多触发 4 次(即 3 次重试),超过就 systemd 放弃并把单元
标记为 failed,配合 OnFailure= 还能进一步触发告警。
timer 单元
# /etc/systemd/system/pgdump.timer
[Unit]
Description=Hourly pg_dump
[Timer]
# 每小时第 5 分钟(避开 nginx logrotate 等整点任务)
OnCalendar=*:05:00
# 错过的执行(如机器关机时)启动时补一次
Persistent=true
# 多机器错峰
RandomizedDelaySec=2m
# 防止上一次还没跑完又起新的
Unit=pgdump.service
[Install]
WantedBy=timers.target
启用
sudo systemctl daemon-reload
sudo systemctl enable --now pgdump.timer
systemctl list-timers pgdump.timer
# NEXT LEFT LAST PASSED UNIT ACTIVATES
# Sat 2026-05-23 14:05:00 CST 22min left Sat 2026-05-23 13:05:00 CST 38min pgdump.timer pgdump.service
OnCalendar 表达式速查
| 表达式 | 含义 |
|---|---|
*-*-* 03:00:00 |
每天 03:00 |
Mon..Fri 09:00 |
工作日 09:00 |
*:0/15 |
每 15 分钟 |
*:0/30:00 |
每 30 分钟(秒级精度) |
2026-05-23 12:00 |
一次性 |
weekly |
每周一 00:00(别名) |
校验表达式:
systemd-analyze calendar 'Mon..Fri 09:30'
# Next elapse: Mon 2026-05-26 09:30:00 CST
OnUnitActiveSec / OnBootSec:相对触发
不基于墙钟,基于上次激活时间:
[Timer]
OnBootSec=15min # 开机后 15 分钟触发首次
OnUnitActiveSec=1h # 之后每隔 1 小时
适合"开机后 N 分钟做一次健康检查 + 之后周期跑"的场景。
失败时告警
# 在 pgdump.service 里加
[Unit]
OnFailure=[email protected]
# /etc/systemd/system/[email protected]
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/notify.sh "%i failed: $(journalctl -u %i -n 30 --no-pager)"
%i 是模板实例名,这里就是 pgdump。notify.sh 调用 Slack webhook /
邮件 / 任意。
日志:journal 自动归档
不需要 >> /var/log/...。所有 stdout / stderr 自动进 journal:
journalctl -u pgdump.service --since '1 day ago'
journalctl -u pgdump.service -p err # 只看 error 级
一次性手动触发
sudo systemctl start pgdump.service # 立刻跑一次,不影响 timer
踩过的坑
Type=oneshot漏写:systemd 默认 simple,脚本退出后认为"挂了"会触发
restart 死循环。所有 cron-style 任务必须Type=oneshot。Wants=network-online.target但脚本仍然连不上:很多 ISP 给 IPv6 默认路由
比 IPv4 慢;加ExecStartPre=/bin/sleep 5简单粗暴。Persistent=true在频繁开关机的笔记本上会变成"开机就跑一堆任务"风暴,
可以加RandomizedDelaySec=10m散开。- 两个 timer 同时触发同一个 service 时只跑一次 —— 这是 feature 不是 bug
(服务的"活跃中"状态不会被重复启动)。
登录后参与评论。