5 月 5 日发布的一篇技术文章主张,Linux 用户在新增定时任务时应优先考虑 systemd timers,而不是把所有周期脚本继续塞进 cron。文章给出的理由并不复杂:timers 能把调度、日志、状态和服务执行纳入同一套 systemd 工具链,排障成本更低。
这不是“cron 已死”的故事。cron 仍是 Unix 世界最耐用的基础工具之一,语法稳定、迁移容易、依赖少。但对今天维护服务器、桌面机、笔记本、自托管服务,或使用 Arch、NixOS 这类发行版的用户来说,定时任务早已不只是“几点跑脚本”,还包括失败追踪、环境隔离、错峰触发和休眠唤醒。
systemd timers 的定位:不是替脚本干活,而是替你管调度
systemd timer 本身不执行脚本,它通常调度同名的 .service。例如 backup.timer 默认触发 backup.service;如果要换目标,需要在 timer 里显式写 Unit=。
| 项目 | cron | systemd timers | 判断 |
|---|---|---|---|
| 执行入口 | crontab 一行命令 | .timer 调度 .service | 结构更清楚,但文件更多 |
| 日志与状态 | 常依赖邮件或脚本自处理 | journalctl、systemctl status | 排障优势明显 |
| 时间表达 | 经典五段式 | OnCalendar、OnBootSec、OnUnitActiveSec | 相对时间是关键增量 |
| 集中触发 | 需自行随机化 | RandomizedDelaySec 等内建 | 更适合多机器场景 |
这里有一个容易踩坑的细节:systemctl start xxx.timer 只是启动计时器,不等于立刻运行服务。要立即执行任务,应启动对应的 .service。而 enable xxx.timer 才是让计时器随系统启动。
真正的差异在可观测性和执行语义
cron 的常见麻烦不是“不能跑”,而是“出事后不好查”。PATH 不同、标准输出被发到本机邮件、脚本环境和交互式 shell 不一致,都会让一次失败变成半小时翻日志。
systemd 也不是魔法。ExecStart= 默认不是普通 shell 行,管道不会自动按 shell 解释,环境变量也不会完整继承。要用管道、$RANDOM、复杂重定向或自定义 PATH,应显式调用 /usr/bin/env bash -c '...',或把逻辑写进脚本,并在 service 里使用绝对路径。这个限制反而是优点:它逼迫任务边界更明确。
时间表达也更贴近真实运维。固定日历时间用 OnCalendar=daily 或完整时间表达;“开机一小时后执行,再每小时执行”则更适合 OnBootSec=1h 加 OnUnitActiveSec=1h。写完后用 systemd-analyze calendar 校验,用 systemctl list-timers 看下一次、上一次和剩余时间,这是 timers 比 crontab 更像运维工具的地方。
错峰、唤醒和限制,决定它能不能当默认方案
在多台机器同时检查更新、备份或请求 API 时,集中触发会制造 thundering herd。systemd 提供 RandomizedDelaySec、FixedRandomDelay、RandomizedOffsetSec,可把任务稳定地摊开,减少同一秒内集体开跑。对自托管用户,这可能意味着备份、同步、证书续期不再挤在午夜。
还有一个桌面用户会关心的能力:WakeSystem= 可在硬件支持时唤醒休眠系统执行任务。比如夜间预取 Arch 或 NixOS 的更新包,早上再手动升级。但服务跑完后是否重新休眠,需要另行处理,timer 不会替用户完成整套电源策略。
接下来最该观察的不是 cron 会不会消失,而是团队是否愿意把定时任务纳入 systemd unit 管理。小型脚本继续用 cron 没问题;新建的备份、清理、更新、轮询任务,如果已经运行在 systemd 系统上,timers 更适合做默认起点。
