systemd timer 替代 crontab 实战:OnCalendar、随机延迟与失败重试

    发布时间:2026-09-22 04:31 更新时间:2026-09-22 04:31 阅读量:0

    用 crontab 跑定时任务,最让人头疼的不是语法,而是它太“沉默”:机器关机期间错过的任务不会补跑,任务失败没有通知,多个任务挤在同一分钟启动容易把磁盘 IO 打满,想查日志还得自己重定向到文件。systemd timer 本质上是把定时触发交给 systemd 统一调度,任务本体仍然是一个普通的 service,于是日志、依赖、重启策略、资源限制这些 systemd 已有的能力全都能复用。本文按“先能跑、再讲可靠性”的顺序,把 timer 与 service 的配合写法、OnCalendar 表达式、随机延迟和失败重试讲清楚。下文命令与配置在主流发行版上通用,个别字段以本机 man systemd.timer 和官方文档为准。

    一、timer 与 service 的配对写法

    systemd 的设计是“一个 timer 触发一个同名 service”。约定俗成的做法是建两个文件:/etc/systemd/system/backup.service 负责干活,/etc/systemd/system/backup.timer 负责什么时候干。timer 里不需要写 ExecStart,只要用 Unit= 指向 service 即可;不写 Unit 时,systemd 会默认寻找同名的 .service。

    # /etc/systemd/system/backup.service
    [Unit]
    Description=Nightly data backup
    
    [Service]
    Type=oneshot
    ExecStart=/usr/local/bin/backup.sh
    User=backup
    

    失败后最多再试 2 次,间隔 5 分钟

    Restart=on-failure RestartSec=5min StartLimitBurst=3 StartLimitIntervalSec=1800

    这里有两个关键点。第一,Type=oneshot 表示任务执行完就退出,systemd 会把它当成一次性的,适合脚本类作业;如果脚本里有后台进程,用 oneshot 会导致 systemd 认为任务已结束而误判,这种情况建议改用 Type=simple 并在脚本里做好前台阻塞。第二,Restart=on-failure 是失败重试的核心,但要注意它和 StartLimitBurst 配合:如果 30 分钟内连续失败超过 3 次,systemd 会停止重试并进入 failed 状态,避免无限重启把机器拖垮。这样设计比 crontab 里手动写循环更可控。

    对应的 timer 文件如下:

    # /etc/systemd/system/backup.timer
    [Unit]
    Description=Run backup every night
    
    [Timer]
    OnCalendar=*-*-* 03:30:00
    RandomizedDelaySec=15min
    Persistent=true
    Unit=backup.service
    
    [Install]
    WantedBy=timers.target
    

    写完执行 systemctl daemon-reload,再 systemctl enable --now backup.timer。注意启用的是 timer 而不是 service,service 由 timer 拉起。验证是否生效可以用 systemctl list-timers,它会列出下一次触发时间,这是排查定时任务最直观的入口。

    二、OnCalendar 语法与随机延迟

    OnCalendar 的格式是 星期 年-月-日 时:分:秒,其中日期部分可以用通配符,也可以用 .. 表示区间、, 表示枚举。它比 cron 的五段式更易读,支持“每 5 分钟”“每月 1 号”这类语义。常见的几种写法:

    # 每天凌晨 3:30
    OnCalendar=*-*-* 03:30:00
    
    

    每 10 分钟一次

    OnCalendar=*:0/10

    每周一、周四 02:00

    OnCalendar=Mon,Thu *-*-* 02:00:00

    每月 1 号 04:15(注意日字段用 01 更清晰)

    OnCalendar=*-*-01 04:15:00

    如果拿不准表达式是否合法,可以用 systemd-analyze calendar 直接验证,它会打印下次触发时间以及归一化后的表达式,这是避免写错日期最省事的办法:

    systemd-analyze calendar "Mon,Thu *-*-* 02:00:00"
    systemd-analyze calendar "*:0/10" --iterations=3
    

    再说 RandomizedDelaySec。当你有几十台机器都跑同一个 timer 时,整点同时触发会造成源站或数据库的瞬时压力。这个参数会在触发时间上叠加一个 0 到设定值之间的随机延迟,把负载摊开。它只影响 timer,不影响 service 的执行逻辑。需要注意的是,随机延迟会让“实际开始时间”变得不确定,如果业务对时间窗口有硬要求(比如必须在 4 点前跑完),随机值就不要设得太大,具体上限结合任务耗时评估。

    三、Persistent 补跑与失败通知

    crontab 最被诟病的一点是机器关机期间错过的任务永远不会补。timer 的 Persistent=true 解决了这个问题:systemd 会记录该 timer 上次触发的时间戳(存放在 /var/lib/systemd/timers/ 下),开机后如果发现已经错过了本该执行的时间点,会立即补跑一次。对于备份、日志归档、对账这类不能漏的作业,这个开关基本必开。

    不过 Persistent 补跑的是“最后一次错过”,如果停机了三天,它不会把三天的任务各补一次,而是只补一次。这一点要在脚本里自行处理增量逻辑。另外补跑发生的时间点是系统启动之后,如果任务本身依赖网络或挂载,记得在 service 里用 After=network-online.target 声明依赖,否则可能因为网络未就绪而失败。

    失败之后怎么知道?两种做法。一种是利用 Restart=on-failure 做自动重试,适合瞬时故障;另一种是配置 OnFailure= 指向一个通知用的 service,在任务最终失败时执行告警脚本,比如发邮件或调用 Webhook。示例:

    # /etc/systemd/system/backup.service 中追加
    [Unit]
    OnFailure=notify@%n.service
    
    

    /etc/systemd/system/notify@.service

    [Unit] Description=Send failure notice [Service] Type=oneshot ExecStart=/usr/local/bin/notify.sh "%i failed"

    调试阶段建议先手动触发一次 service,确认脚本能跑通,再看 timer 是否按时拉取:systemctl start backup.service 看退出码,journalctl -u backup.service -n 50 看日志输出。如果 timer 没触发,优先检查 systemctl list-timers --all 里状态是否为 active,以及是否被 systemctl mask 屏蔽。整体迁移时,建议一个任务一个 service 加一个 timer,命名保持对应,方便后续维护和批量排查,具体细节以本机 systemd 版本的官方文档为准。

    继续阅读

    📑 📅
    Docker容器时间漂移与crond定时任务错乱:TZ与宿主时间源协同 2026-09-21
    MySQL 授权与远程访问配置实战:user@host 匹配规则、bind-address 与防火墙三层放行检查 2026-09-21
    Nginx与PHP上传目录权限:www-data、umask与0777的坑 2026-09-21
    systemd-resolved 与 /etc/resolv.conf 冲突排查 2026-09-21
    MySQL索引失效排查:EXPLAIN执行计划与隐式转换定位 2026-09-21
    Nginx 代理 WebSocket 频繁断连:Upgrade 头、超时与保活配置 2026-09-22
    服务器被DDoS打满带宽:自查连接分布与限速缓解 2026-09-22
    Linux磁盘IO高却查不出:iotop与blkio限速实战 2026-09-22
    Nginx WebSocket 与 SSE 并存:proxy_buffering 冲突排查 2026-09-22
    MySQL备份文件损坏怎么验证:一致性校验与恢复演练 2026-09-22