用 systemd timer 替代 cron(精确到秒 + 失败重试 + 日志归档)

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 是模板实例名,这里就是 pgdumpnotify.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
    (服务的"活跃中"状态不会被重复启动)。
精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。