关于本文
本文是通过利用生成式 AI 的自动生成流程创建的。我们查阅了 systemd-analyze、systemd.timer 和 systemd.time 的官方手册,并编写了一个以只读方式检查下一次启动时间的示例。目前尚未在目标 Ubuntu 系统上进行执行验证。
验证状态:📘 已查阅 systemd 官方手册,尚未在 Ubuntu 实机上验证
在 Ubuntu 中*.timer有时服务并未在预期的时间运行。常见的误解是认为日历表达式正确就等同于实际定时器已启用这一观点。
systemd-analyze calendar通过使用OnCalendar=,无需启用定时器,即可解析与Persistent、AccuracySec相同格式的表达式,并列出下一次预计执行的日期和时间。此外,分时区进行检查可以减少遗漏。
最小实验:尝试计算每 2 小时的时间
例如,如果希望在日本时间的 00:05、02:05、04:05、…、22:05 执行,日历表达式应编写如下。
[Timer] OnCalendar=*-*-* 00,02,04,06,08,10,12,14,16,18,20,22:05:00 Asia/Tokyo Persistent=false
这是一个配置示例。尚未启用服务或定时器。
请在 Ubuntu 中运行以下命令。
systemd-analyze calendar --iterations=8 \ '*-*-* 00,02,04,06,08,10,12,14,16,18,20,22:05:00 Asia/Tokyo'
此操作不需要sudo,也不会启动 WordPress 等生产服务。它只是解析日期和时间语法,并计算接下来的 8 次触发计划。输出的年月日和星期几会根据执行时的不同而变化。
Original form: ... Asia/Tokyo Normalized form: ... Next elapse: (次の偶数時05分に相当する日時) Iter. #2: (さらに2時間後) ...
※以上为显示项目的示例,并非实机日志的转录。
修改一处进行观察
我想确认的是,每小时整点(00分)与每两小时一次的区别。
# 毎時00分 systemd-analyze calendar --iterations=4 \ '*-*-* *:00:00 Asia/Tokyo' # 偶数時05分 systemd-analyze calendar --iterations=4 \ '*-*-* 00,02,04,06,08,10,12,14,16,18,20,22:05:00 Asia/Tokyo'
两者的输出中Next elapse与Iter. #2之间的间隔。前者应该按大约1小时间隔计算,后者按大约2小时间隔计算。这样一来,无需运行实际服务,就能确认预期的计划是否已正确转化为表达式。
Daily-Code-Samples的脚本中,我保存了将这些确认步骤整合到一起的preview-calendar.sh。你可以将任意日历表达式作为参数传入。
bash preview-calendar.sh bash preview-calendar.sh '*-*-* 08:30:00 Asia/Tokyo'
“计划时间正确”与“实际运行成功”的区别
| 要调查的内容 | 确认对象 | 可知晓的事项 |
|---|---|---|
| 日历表达式的语法 | systemd-analyze calendar | 下一次触发计划 |
| 定时器是否已注册并启用 | systemctl list-timers --all | 当前管理的定时器 |
| Unit的定义 | systemctl cat 名前.timer | OnCalendar和Persistent等设置 |
| 最近的服务执行结果 | systemctl status 名前.service | 当前的进程状态与最近一次执行 |
| 错误详情 | journalctl -u 名前.service | 服务日志 |
即使日历表达式正确,如果 timerinactive或disabled处于某种状态,通常也不会按计划自动执行。此外,即使定时器运行,如果服务本身失败,处理也不会完成。
接下来的操作是仅读取现有设置。请将调查对象替换为您自己管理的名称。
systemctl list-timers --all --no-pager systemctl cat example.timer systemctl show example.timer \ -p ActiveState -p Unit -p NextElapseUSecRealtime -p LastTriggerUSec journalctl -u example.service --no-pager -n 30
指定找不到的 Unit 并不合适。请在确认实际的 timer 名称后再进行替换。根据查看权限,有时可能无法读取部分 Journal。
将 Persistent 设为 true 会发生什么?
Persistent=true是一项设置,当基于 OnCalendar 的定时器在停止或未运行期间错过触发计划时,允许在重新启动等情况下进行追赶运行。
| 设置 | 主要含义 | 实务上的注意事项 |
|---|---|---|
Persistent=false | 不要设置为无条件补回停止期间的触发 | 可以更容易地防止发布、计费处理等意外的延迟执行 |
Persistent=true | 允许追赶运行在停止期间错过的日历执行 | 确认恢复后是否会发生意料之外的执行 |
AccuracySec | 触发时间的精度窗口 | 有时可能并非刚好在预定时间 |
容易产生误解的是,Persistent=true最容易陷入的误区是认为“会把错过的部分一件一件全部重新执行”。这是无法保证的。定时器只是一种启动服务的机制,它不会管理待处理队列的数量或发布内容是否重复。
例如,如果每2小时发布一篇博客,千万不能因为服务器停机了12小时,就一口气把过去的6篇全部发布。应当在定时器中使用限制延迟触发的设置,并在应用层检查“是否仅限当天”以及“在此2小时时间段内最多1篇”。
当存在时区差异时
OnCalendar=*-*-* 08:30:00如果只写了这些,主机的时区设置就会产生影响。如果云服务器使用的是UTC时间,它将与在日本看到的08:30不一致。
timedatectl date '+%Y-%m-%d %H:%M:%S %Z' systemd-analyze calendar '*-*-* 08:30:00 Asia/Tokyo'
通过采用日历格式的写法Asia/Tokyo,可以明确时间的基准。但是,如果服务器的时钟同步状态本身就不准确,即使日历的解析正确,实际动作也会发生偏差。
AccuracySec与服务处理时间
OnCalendar即使指定了具体的时间,AccuracySec由于精度范围以及服务等待启动等原因,并不保证能与指定的秒数完全一致。不仅要排查常见的Linux性能问题,还要检查Unit的配置。
此外,相同的服务是否正在运行、服务执行需要多长时间,也是运维中的重要条件。即使定时器的计划是每2小时一次,如果应用的设计是无限期重试,也无法做到每2小时处理一次的上限。
将责任明确划分为:时间控制交由timer、对象有效性交由应用、执行结果交由日志或数据库,这样将更容易排查原因。
实务中需要确认的5个项目
OnCalendar是否可以用systemd-analyze calendar进行评估。是否明确了时区。
timer本身是否有效,并且拥有下一次的计划执行时间。
Persistent以及AccuracySec是否为预期行为?是否核对过触发后的服务终止状态及日志?
按此顺序确认,即可区分“时间指定错误”、“定时器无效”、“服务异常终止”以及“实际业务处理量为0”等不同原因。

