Linux systemd.timer 日历与单调时间的选择
来源:17golang原创
时间:2026-10-28 13:14:00 430浏览 收藏
Linux 的 systemd.timer 选错时间基准时,配置表面上能启动,到了校时、重启或挂起场景却可能出现“提前触发”“错过不补”或周期漂移。选择方法其实很明确:任务关心“几点几分、星期几”就用 OnCalendar=;任务关心“启动后经过多久、服务再次激活后多久”就用 OnBootSec=、OnActiveSec= 等单调时间指令。
官方地址:https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html
下面只围绕日历时间和单调时间的选择展开,并把挂起、系统校时、持久化补偿和触发精度放到同一个判断框架里。
先按任务语义区分两类时钟
OnCalendar= 使用墙上时钟,也就是系统的实时钟和时区规则。它适合“每天 02:00 清理”“每周一 09:30 汇总”这类固定民用时间。日历表达式由 systemd.time 解析,可以写星期、日期、时间和时区。
OnBootSec=、OnStartupSec=、OnActiveSec=、OnUnitActiveSec= 与 OnUnitInactiveSec= 则描述相对时长。它们不依赖当前是几点,也不因时区改变而改变语义,适合“机器启动 10 分钟后做一次”“服务每次激活后 15 分钟再触发”这类任务。

固定日期和周期日期写成 OnCalendar
日历任务的核心是把业务规则写成“下一次应落在哪个墙上时间”。例如夜间归档应该使用 OnCalendar=*-*-* 02:00:00,而不是启动后固定等待若干小时;否则主机中午重启时,归档时刻也会跟着漂移。
[Unit]
Description=夜间归档任务
[Timer]
# 固定在每天凌晨两点,表达业务日历而不是启动后的延迟
OnCalendar=*-*-* 02:00:00
# 机器离线时不运行,恢复后是否补跑由 Persistent 决定
Persistent=true
# 让同一主机上的多个定时器不要都挤在精确同一秒唤醒
AccuracySec=1min
[Install]
# 允许 systemctl enable 生成开机启动关联
WantedBy=timers.target
Persistent=true 只对 OnCalendar= 有意义:systemd 会保存上次触发时间,定时器重新激活时,如果停用期间本应触发过,服务会得到一次补偿触发。它不是“每个错过的周期都补一遍”,也不是单调计时器的通用补偿开关。
如果任务必须按某个地区的当地时间运行,把时区写进表达式比依赖主机默认时区更清楚:
[Timer]
# 用明确时区表达当地营业日,避免主机时区变化造成歧义
OnCalendar=Mon..Fri *-*-* 09:30:00 Asia/Shanghai
# 让系统在可接受的窗口内合并唤醒
AccuracySec=30s
启动后延迟使用 OnBootSec 或 OnStartupSec
“启动后等待一段时间”不是日历问题。OnBootSec=10min 相对系统启动计时,适合启动后预热、延迟清理和开机后的一次性准备。OnStartupSec= 相对 systemd 用户实例或系统管理器启动,具体使用哪一个取决于 timer 所在的管理器。
[Timer]
# 每次系统启动后等待十分钟,再触发同名服务
OnBootSec=10min
# 让 timer 被重新激活后也重新开始等待
OnActiveSec=10min
# 单调时间不是精确墙上时刻,按可接受的窗口合并唤醒
AccuracySec=30s
这里有一个容易忽略的边界:如果使用 OnBootSec= 或 OnStartupSec= 时,配置的目标时刻在 timer 激活时已经过去,systemd 可以立即触发服务;这和日历表达式错过某个墙上时刻的处理语义不同。需要严格的每日时刻时,不要用“启动后若干小时”代替 OnCalendar=。
按服务生命周期选择 OnUnitActiveSec 和 OnUnitInactiveSec
如果间隔应该跟着被触发服务的生命周期走,就用 OnUnitActiveSec= 或 OnUnitInactiveSec=。前者相对服务上次激活,后者相对服务上次进入非活动状态。它们表达的是服务关系,不是“主机每天某时刻”。
[Timer]
# 服务激活后两小时再安排下一次触发
OnUnitActiveSec=2h
# 服务停用后十五分钟触发一次后续处理
OnUnitInactiveSec=15min
# 两个表达式任意一个到期,都可以触发关联服务
AccuracySec=1min
这类配置适合缓存刷新、短周期同步或服务完成一轮工作后的再次调度。若服务运行时间可能长于间隔,要特别关注实际的再次触发行为;新版本 systemd 还提供了只针对 OnCalendar= 的 DeferReactivation=,不能把它当成单调计时器的通用“防重入”开关。真正需要互斥时,应在服务本身设计锁、幂等和失败恢复。
挂起与校时决定了边界行为
默认单调计时器使用暂停时也会停住的单调时钟,因此笔记本睡眠两小时后,OnBootSec=3h 并不一定按现实世界过去的三小时计算。设置 WakeSystem=true 后,systemd 会使用在挂起期间继续推进的时钟,并在硬件支持时唤醒系统。
[Timer]
# 需要覆盖挂起时间时,使用可在挂起期间继续推进的时钟
OnBootSec=3h
WakeSystem=true
# 唤醒能力依赖系统权限与硬件,不能替代业务层的幂等处理
AccuracySec=1min
反过来,备份、报表和账务结算通常关心民用时间,且可能需要补跑,这时更适合 OnCalendar= 加 Persistent=true。不要用 WakeSystem=true 去模拟“错过日历任务后补跑”,两者解决的是不同问题。
所有带 OnCalendar= 的 timer 都与系统实时钟有关。系统刚启动、时钟尚未完成校准时,准确时间任务应与 time-sync.target 的依赖关系保持清楚;如果只是接受大致单调的本地时间,不要为了形式强行等待远端时间服务。

用 AccuracySec 和检查命令收束配置
systemd.timer 的到期并不等于一定在配置的那一微秒启动服务。AccuracySec= 默认允许一个时间窗口,systemd 会在窗口内安排唤醒,以合并多个定时器的开销;需要精确时刻时再把它调小,但也要接受更多唤醒和更高功耗。
提交前可以只检查表达式和最终展开的单元内容,不需要把定时器改成另一种时间基准:
# 规范化日历表达式,并查看下一次匹配时间
systemd-analyze calendar 'Mon..Fri *-*-* 09:30:00 Asia/Shanghai'
# 读取合并 drop-in 后的最终 timer 配置
systemctl cat archive.timer
# 查看当前状态、上次触发和下一次触发时间
systemctl status archive.timer
判断时可以记住这张速查表:
| 问题 | 优先指令 | 关键边界 |
|---|---|---|
| 每天固定几点执行 | OnCalendar= | 时区、校时、Persistent= |
| 开机后等待一段时间 | OnBootSec= | 默认挂起会暂停,激活时刻已过去可能立即触发 |
| timer 激活后等待 | OnActiveSec= | 相对 timer 激活,不是当天固定时刻 |
| 跟随服务上次激活或停用 | OnUnitActiveSec= / OnUnitInactiveSec= | 间隔跟服务生命周期变化 |
| 睡眠期间也要计时 | 单调指令 + WakeSystem=true | 依赖硬件、权限和幂等设计 |
最终原则只有一句:把“业务时刻”交给 OnCalendar=,把“经过时长”交给单调计时器;再分别配置补偿、挂起和精度。只要先回答任务依赖哪一种时间,systemd.timer 的选择就不会被重启、时区或睡眠场景带偏。
常见追问
OnCalendar 和 OnBootSec 可以同时写吗?
可以。同一个 timer 中多个表达式任意一个到期都可以触发关联服务,但要确认这是不是业务真正需要的“多入口触发”,并为服务做好幂等。
Persistent=true 能补齐所有错过的运行吗?
不能。它用于日历定时器重新激活时判断停用期间是否至少错过过一次,不是历史任务队列,也不会把每个周期逐条回放。
AccuracySec=1s 就一定精确到秒吗?
不一定。它只是 systemd 定时器的调度窗口,仍受系统调度、服务启动耗时和更底层的时间机制影响。需要严格时间语义时,应在服务内部记录实际时间并设计容错。
-
422 收藏
-
208 收藏
-
417 收藏
-
426 收藏
-
387 收藏
-
223 收藏
-
225 收藏
-
301 收藏
-
216 收藏
-
108 收藏
-
434 收藏
-
112 收藏
-
404 收藏
-
118 收藏
-
117 收藏
-
382 收藏
-
123 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习