登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

systemd timer 的 Persistent 为什么能补跑错过任务

来源:17golang原创

时间:2026-10-05 22:58:45 294浏览 收藏

一台服务器每天 02:30 生成报表,但它在 02:00 关机维护,04:00 才重新启动。普通日历定时器会继续等待下一个 02:30;加入 Persistent=true 后,systemd 会在 timer 再次激活时检查持久化的上次触发时间。如果 OnCalendar= 在未激活期间至少应该到点一次,就补触发一次对应服务。

Persistent 的本质不是保存“待执行任务队列”,而是保存上次触发时间,并在 timer 激活时完成一次是否错过日历时刻的判断。停机期间错过 1 次或 10 次,恢复后通常都只产生一次 service 激活。

这个边界很重要:它只对带 OnCalendar= 的 timer 生效,不会让 OnBootSec=、OnUnitActiveSec= 等单调时钟配置自动获得同样的补跑语义。下面用一个每日同步任务把配置、激活和检查动作串起来。

补跑判断的核心关系

根据 systemd 当前的 timer 文档,启用 Persistent= 后,服务最后一次被 timer 触发的时间会保存到磁盘。timer unit 后来再次激活时,systemd 会把这个时间与当前时间及 OnCalendar= 计划比较;只要未激活期间至少存在一个应触发时刻,就触发服务。这个判断发生在 timer 激活时,所以只写配置但没有启动 timer,并不会凭空执行任务。

systemd OnCalendar、Persistent 持久时间与 service 单次补触发的静态关系图
图1:静态关系说明图,展示日历计划、磁盘状态与单次补触发之间的边界;它不是运行截图。

还要分清“关机”和“睡眠”。对于 OnCalendar=,机器挂起期间实时时钟不会暂停;恢复时,睡眠期间到点的日历 timer 会被处理。如果连续睡眠期间到点多次,同样只会形成一次服务激活。Persistent=true 主要补上的是 timer 未激活或系统关机导致的缺口。

所谓“立即补跑”也有一个配置层面的例外:如果 timer 设置了 RandomizedDelaySec=,补触发仍会受到随机延迟约束。因此判断现场时,不要只看开机后的几秒钟;应同时查看 timer 的 NEXT、LAST 和该随机延迟配置。

把计划、执行与观察拆成三个边界

systemd 推荐把任务命令放进 .service,把触发计划放进同名 .timer。例如 report-sync.timer 默认会激活 report-sync.service。这样做的好处是:计划是否到点、命令是否成功、补跑是否发生,分别有清晰的检查入口。

report-sync timer、service、timers.target 与 systemctl 观察入口的静态模块关系图
图2:模块关系说明图,展示计划、执行与可观测状态的分工;它不是终端或界面截图。

先定义只负责执行任务的 service

创建 /etc/systemd/system/report-sync.service。示例中的命令只是占位,实际使用时应替换成自己的同步脚本,并保证脚本重复执行不会制造重复数据。

[Unit]
# 说明这个一次性任务的用途
Description=Synchronize daily report data

[Service]
# 每次触发执行一次,命令结束后 service 退出
Type=oneshot
# 替换成实际任务;脚本应能安全地重复执行
ExecStart=/usr/local/bin/report-sync

补跑通常发生在系统刚恢复、网络尚未稳定或外部依赖仍在启动的阶段。如果任务强依赖网络,可以在 service 的 [Unit] 中按实际环境增加对网络就绪目标的依赖;不要仅凭 timer 已触发就假设业务依赖必然可用。

再定义带 Persistent 的日历 timer

创建 /etc/systemd/system/report-sync.timer:

[Unit]
# timer 只负责计划,不直接承载业务命令
Description=Run report synchronization every day

[Timer]
# 每天本地时间 02:30 形成一个日历触发点
OnCalendar=*-*-* 02:30:00
# timer 重新激活时检查未激活期间是否错过日历触发点
Persistent=true
# 允许 systemd 在一分钟窗口内合并唤醒
AccuracySec=1min

[Install]
# 开机进入 timers.target 时加载这个 timer
WantedBy=timers.target

AccuracySec=1min 表示允许一定精度窗口,它不是失败重试,也不是随机延迟。若业务要求更精确,可以调小;若大量主机使用相同计划并希望错峰,应另外评估 RandomizedDelaySec=,同时接受补跑可能被延后的结果。

启用之前先核对日历表达式

systemd-analyze calendar 能解析日历表达式并列出下一次触发时间,适合在加载 unit 前发现时区、星期或日期写法错误。

# 解析表达式并显示规范形式及下一次触发时间
systemd-analyze calendar '*-*-* 02:30:00'

确认表达式后重新加载 unit,启用并立即启动 timer:

# 让 systemd 重新读取新增或修改过的 unit 文件
sudo systemctl daemon-reload

# 建立开机启用关系,并在当前系统立即激活 timer
sudo systemctl enable --now report-sync.timer

enable 负责下次启动时随 timers.target 激活,--now 负责当前这一次立即启动。只执行 enable 而不启动时,本次运行周期里 timer 可能仍未激活,也就不会立即进行 Persistent 的补跑判断。

用三个检查点确认计划和补跑

检查 timer 是否真的在等待

# 查看 NEXT、LEFT、LAST、PASSED、UNIT 和 ACTIVATES
systemctl list-timers --all report-sync.timer

# 查看加载状态、启用状态和下一次触发信息
systemctl status report-sync.timer

NEXT 是下一次计划时间,LAST 是最近一次触发时间,ACTIVATES 应指向 report-sync.service。如果列表中完全没有该 timer,先检查 unit 是否加载成功以及是否已启动,而不是直接怀疑 Persistent。

检查 service 是否被补触发

# 查看本次开机后 service 的触发与退出记录
journalctl -b -u report-sync.service

# 连同上一次开机记录检查关机前后的触发情况
journalctl -b -1 -u report-sync.service

判断标准不是“日志里出现 Persistent 字样”,而是:关机前最后触发时间早于一个或多个计划点,timer 在恢复后重新激活,并且 service 随后出现一次新的启动记录。若设置了随机延迟,应把延迟窗口纳入观察。

需要受控验证时怎样操作

在测试机上可以临时选择较短但仍是 OnCalendar= 的计划,先让 timer 正常触发一次,再停止 timer,使一个计划点在停止期间经过,最后重新启动 timer。若 Persistent=true 生效,重启 timer 后应看到 service 新增一次启动记录。不要在生产机用修改系统时间的方式测试,它会干扰日志顺序、证书和其他日历任务。

# 暂停 timer,让一个 OnCalendar 计划点在未激活期间经过
sudo systemctl stop report-sync.timer

# 经过计划点后重新激活;此时会执行 Persistent 判断
sudo systemctl start report-sync.timer

# 观察 service 是否只新增了一次启动记录
journalctl -u report-sync.service --since '15 minutes ago'

最容易误判的五个地方

误以为每个错过时刻都会逐个回放

Persistent 判断的是“未激活期间是否至少错过一次”。每天执行一次的任务若停机三天,恢复后不是连续执行三遍,而是补一次。若业务必须逐日补齐,service 自己需要根据业务水位、日期分区或游标计算缺口;timer 不能替代业务级补偿队列。

把单调时钟 timer 当成日历补跑

Persistent= 只对 OnCalendar= 有效。OnBootSec= 与 OnStartupSec= 在激活时若目标时刻已经过去,本身有立即到期规则,但这不是 Persistent 保存历史日历触发点的机制。OnUnitActiveSec= 则表示相对上次激活的间隔,也不能用 Persistent 把停机期间的多个间隔逐个还原。

只启用 service,没有启用 timer

应启用 report-sync.timer,而不是把一次性 report-sync.service 设为开机常驻。timer 激活后才会维护计划、持久时间和下一次触发信息。

服务已经运行时期待它被重复启动

timer 到点只会请求激活目标 unit。如果目标 service 当时已经处于 active 状态,systemd 不会因为 timer 再到点就自动重启它。长任务应结合运行时长、并发策略和任务锁设计,不能把 Persistent 当成并发执行开关。

忽略时间同步与时区

OnCalendar= 使用实时时钟。系统时间、时区或 RTC 不正确,会直接影响日历判断。systemd 会为带 OnCalendar 的 timer 添加与时间设置、时间同步目标相关的排序关系,但主机本身仍应有可靠的时间同步配置。

配置与判断速查表

需求或现象关键配置/检查实际语义
关机期间错过日历任务OnCalendar= + Persistent=truetimer 激活时补触发一次
停机期间错过多个计划点检查业务是否自行补齐日期systemd 不逐条回放,只激活一次 service
开机后没有立刻补跑检查 timer active 状态和 RandomizedDelaySec=补触发可能受随机延迟约束
确认表达式systemd-analyze calendar解析计划并显示下一次触发时间
确认 timer 计划systemctl list-timers --all查看 NEXT、LAST 与 ACTIVATES
确认任务执行journalctl -u report-sync.service查看 service 的真实启动与退出记录
清除持久时间状态停止 timer 后使用 systemctl clean --what=state删除 Persistent 维护的时间戳状态

结论

Persistent=true 能补跑,是因为 systemd 把上次服务触发时间保存到磁盘,并在 timer 重新激活时拿它与 OnCalendar= 计划做比较。它解决的是“至少错过一次后补一次”,不是历史任务逐条回放。把 timer 正确启用、让业务命令保持幂等,再用 list-timers、status 和 journalctl 分别核对计划、状态与执行记录,才能判断补跑是否按预期发生。

相关问题

Persistent=true 会在第一次创建 timer 时补跑吗?

不要把“首次创建”理解为天然存在历史欠账。补跑判断依赖 timer 激活时可用的持久状态和日历计划;新部署时应以实际的 LAST、状态文件和 service 日志为准,不要假设部署前的所有日历时刻都会被回放。

服务器睡眠期间也需要 Persistent 吗?

OnCalendar 使用实时时钟,系统从睡眠恢复后会处理睡眠期间已经到点的日历 timer;多次到点仍只形成一次服务激活。Persistent 的核心价值是覆盖 timer 未激活和系统关机的情况。

怎样彻底移除 Persistent 留下的状态?

先停止 timer,再按官方文档使用 systemctl clean --what=state report-sync.timer 清理该选项维护的时间戳文件。卸载 timer unit 前做这一步,可以避免遗留状态影响之后同名 unit。

参考资料:systemd.timer 官方源码文档、systemd.time 官方源码文档、systemd-analyze 官方源码文档。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>