Linux 基础体系 · 第 13/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
Linux 定时任务:cron、systemd timer、时区、防重入和错过执行
定时任务看似只是“到点执行一条命令”,但生产问题通常来自四个边界:
- 时间表达式到底由谁解释:cron 还是 systemd?
- 时间使用哪个时区:系统时区、任务时区,还是应用自己的时区?
- 上一次任务尚未结束时怎么办:允许并发、跳过,还是排队?
- 机器关机、服务停止或系统休眠期间错过了计划时间怎么办?
要准确回答这些问题,不能只记住一行配置语法,还需要区分“计划触发”和“任务执行”。
一、先建立模型:定时器不等于任务进程
一个定时任务通常包含三个组件:
时间规则 ──> 调度器 ──> 启动任务
│
├─ 脚本或程序
├─ 日志
├─ 锁
└─ 外部资源
例如:
- cron 读取 crontab,匹配当前时间,然后创建命令进程;
- systemd timer 根据时间规则激活一个
.service单元; .service再启动脚本、二进制程序或其他服务。
因此,下面两个概念必须分开:
1. 触发时间
调度器认为“应该尝试启动任务”的时间点,记为:
例如“每天 03:00”在某个时区中会产生一组计划时间。
2. 实际执行
任务真正开始运行的时间点,记为:
理想情况下, 接近 ,但实际可能存在:
- 调度器延迟;
AccuracySec或随机延迟;- 机器关机导致未触发;
- 上一次任务仍在运行;
- 任务启动失败;
- 锁导致本次执行主动退出。
“定时任务是否可靠”不能只看时间表达式,还要观察从 到 的整个路径。
二、cron:按墙上时间匹配的传统调度器
cron 是一类长期运行的调度守护进程。常见实现包括:
- Debian、Ubuntu 常见的
cron; - Fedora、RHEL 系常见的
cronie; - BusyBox 提供的简化实现。
具体选项和扩展会有差异,因此生产环境应以本机的 man 5 crontab 和 man 8 cron 为准。
2.1 用户 crontab 和系统 crontab
编辑当前用户的 crontab:
crontab -e
查看当前用户的配置:
crontab -l
系统级任务可能位于:
/etc/crontab
/etc/cron.d/*
/etc/cron.hourly/*
/etc/cron.daily/*
用户 crontab 的基本格式有五个时间字段:
分钟 小时 日 月 星期 命令
例如:
0 3 * * * /usr/local/sbin/backup.sh
表示在当前 cron 使用的时间基准下,每天 03:00 尝试执行脚本。
而 /etc/crontab 和 /etc/cron.d/* 通常多一个用户字段:
0 3 * * * backup /usr/local/sbin/backup.sh
这里的 backup 表示以该用户身份运行。把两种格式混用会导致任务完全不按预期执行。
2.2 cron 表达式的“日”语义
cron 的五个字段通常是:
分钟:0-59
小时:0-23
日:1-31
月:1-12 或名称
星期:0-7,通常 0 和 7 都表示星期日
有一个容易误判的规则:当“日”和“星期”同时被限制时,传统 cron 通常采用 二者满足其一即可,而不是逻辑与。
例如:
0 0 1 * 1 /usr/local/bin/report
常见实现会在以下时间执行:
- 每月 1 日;
- 每周一。
它不是“每月 1 日且恰好是星期一”。
如果业务需要严格的逻辑与,应让 cron 每天触发,再由脚本显式判断日期,或者使用 systemd 的更清晰日历表达式。
2.3 cron 的命令环境很小
cron 不会继承交互式登录 Shell 的完整环境。典型差异包括:
PATH较短;- 当前工作目录通常不是项目目录;
- 不会自动读取用户的
.bashrc; - 不能假设交互式终端存在;
- 重定向、引号和通配符仍由 Shell 解释。
不可靠的写法:
0 3 * * * backup.sh
更可控的写法:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/local/sbin/backup.sh >>/var/log/backup.log 2>&1
脚本自身也应使用绝对路径,明确工作目录,并正确处理错误:
#!/usr/bin/env bash
set -Eeuo pipefail
cd /srv/app
/usr/bin/rsync -a --delete ./ /backup/app/
set -e 并不能代替完整的错误设计。例如管道、条件命令和子 Shell 有特殊规则;这属于 Shell 工程问题,不应把 cron 的成功启动误认为脚本成功完成。
三、cron 的时间语义、时区和错过执行
3.1 cron 通常按本地墙上时间判断
cron 的调度基础是“墙上时钟”,也就是操作系统提供的日历时间,而不是一个简单的“启动后经过了多少秒”。
查看主机时区和时间同步状态:
timedatectl
典型输出可能包含:
Local time: Tue 2025-03-11 10:20:00 CST
Universal time: Tue 2025-03-11 02:20:00 UTC
Time zone: Asia/Shanghai (CST, +0800)
System clock synchronized: yes
这里至少有三个不同概念:
- UTC:全球统一的时间基准;
- 系统时区:主机把 UTC 显示成本地时间的规则;
- 任务时区:某个调度表达式采用的时区。
不要把脚本中的 TZ=... 和 cron 调度时区混为一谈。前者可能只影响进程内的时间库和命令输出,未必改变 cron 什么时候触发任务。
部分 cron 实现支持在 crontab 中指定:
CRON_TZ=Asia/Shanghai
0 3 * * * /usr/local/sbin/backup.sh
这依赖具体实现。CRON_TZ 在常见实现中用于解释调度时间,但它是否作为普通环境变量传给命令、系统级 crontab 是否支持,不能脱离发行版实现假设。部署前应使用本机的 cron 文档验证。
如果任务是跨地域运行、必须在固定业务时区执行,systemd timer 的时区表达能力通常更容易检查;另一种可靠方案是统一用 UTC 调度,再在业务代码中处理展示时区。
3.2 夏令时会破坏“每天某个本地时间一次”的直觉
假设任务规定为当地时间每天 02:30:
- 春季向前拨钟时,02:30 可能根本不存在;
- 秋季向后拨钟时,某个本地时间可能出现两次。
因此,下面这个命题并不总成立:
0 2 * * *每个自然日恰好执行一次。
cron 实现对夏令时跳过或重复时间的处理并不完全统一。某些实现会特殊处理小于数小时的时间跳变,不能把一种实现的行为推广到所有发行版。
需要“每天一次”的任务应设计幂等性,并记录业务日期。例如任务处理的是某个结算日:
业务日期 = 上一次已经成功处理的日期 + 1
而不是简单地假设“本次进程启动时的本地日期就是要处理的日期”。
3.3 cron 默认不会补执行
cron 通常只在守护进程运行并观察到匹配时间时执行任务。
例如:
03:00 机器关机
07:00 机器启动
如果任务是 0 3 * * *,cron 一般不会在 07:00 自动补执行 03:00 的任务。它会等待下一个匹配时间。
这意味着:
- 重启期间错过的任务不会自动恢复;
- cron 守护进程停止期间错过的任务通常不会自动恢复;
- 长时间休眠时是否补执行取决于实现,不能假设;
@daily不是可靠的“每 24 小时一次”补偿机制。
如果业务要求“至少完成每个业务日”,应在任务中持久化进度,启动时扫描缺失日期并补处理,而不是只依赖 cron 的触发次数。
四、systemd timer:用 Unit 和 Service 表达调度
systemd timer 是 systemd 的一种 Unit。它本身通常不执行备份逻辑,而是激活同名的 service:
backup.timer ──> backup.service ──> /usr/local/sbin/backup.sh
例如:
/etc/systemd/system/backup.timer
/etc/systemd/system/backup.service
backup.timer 默认关联:
backup.service
也可以通过 Unit= 显式指定目标服务。
4.1 一个可运行的完整示例
创建服务:
# /etc/systemd/system/backup.service
[Unit]
Description=Application backup
[Service]
Type=oneshot
User=backup
Group=backup
ExecStart=/usr/local/sbin/backup.sh
创建定时器:
# /etc/systemd/system/backup.timer
[Unit]
Description=Run application backup daily
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
AccuracySec=1min
[Install]
WantedBy=timers.target
加载、启用并立即查看:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl status backup.timer
systemctl list-timers --all backup.timer
这里每一步的含义是:
daemon-reload让 systemd 重新读取新文件;enable创建开机启动关系;--now同时立即启动 timer;list-timers显示上次触发时间和下一次预计触发时间;- timer 触发时,systemd 激活
backup.service; Type=oneshot表示该服务运行一次命令,命令退出后服务结束。
检查服务日志:
sudo journalctl -u backup.service
sudo journalctl -u backup.service -b
backup.timer 的日志和 backup.service 的日志是两个不同的对象。只查看 timer 日志,不能证明脚本成功完成。
4.2 OnCalendar 和单调时间
systemd timer 有两类主要时间:
日历时间
[Timer]
OnCalendar=Mon..Fri *-*-* 03:00:00
它基于日期、小时、分钟等墙上时间,类似 cron,但表达式更丰富。
用 systemd-analyze 检查表达式,不必等到第二天:
systemd-analyze calendar 'Mon..Fri *-*-* 03:00:00'
该命令会显示解析结果和下一次匹配时间。它适合发现:
- 日期字段写错;
- 星期范围不符合预期;
- 当前时区导致的下一次时间不同;
- 表达式实际上无法产生计划时间。
也可以指定时区,例如:
OnCalendar=*-*-* 03:00:00 Asia/Shanghai
具体日历语法以本机 man systemd.time 为准。固定业务时区时,应明确写出时区,而不是依赖主机部署时的默认时区。
单调时间
[Timer]
OnBootSec=15min
OnUnitActiveSec=1h
这些规则基于“启动后经过多久”或“单元激活后经过多久”,而不是某个日历时间。
单调时间适合:
- 开机后延迟启动;
- 服务激活后一段时间再次检查;
- 不关心当地日期和夏令时的周期任务。
它不适合表达“每天凌晨三点”。
单调时钟通常不因手工修改系统墙上时间而倒退,但系统休眠对计时的影响需要区分。普通单调计时器可能在挂起期间暂停计时;需要把挂起时间也计入计时的场景,应检查 WakeSystem= 和本机 systemd 文档,并验证底层硬件是否支持唤醒。
五、systemd timer 的延迟、合并和随机化
5.1 AccuracySec 不是“最多延迟多少秒”
[Timer]
AccuracySec=1min
AccuracySec 表示 systemd 允许把定时器触发时间放在一个精度窗口内,以便合并多个定时器、减少唤醒和调度开销。
它不是一个严格的“任务一定在 60 秒内启动”的实时保证,也不是任务执行时长的限制。
如果任务必须尽量靠近计划时间,可以缩小该值,但这会增加唤醒和调度开销;即使设置得很小,也不能消除:
- CPU 调度延迟;
- I/O 阻塞;
- 系统负载;
- 服务启动失败;
- 锁竞争。
5.2 RandomizedDelaySec 会主动增加延迟
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=20min
这适合大量机器同时在整点执行同一任务的场景,可以打散负载。但它改变了“03:00 执行”的语义:任务可能在 03:00 之后随机延迟。
因此不能同时把“严格时间点”和“随机错峰”当作无条件保证。若任务有截止时间,应在业务中记录计划时间和实际开始时间,并监控延迟。
六、错过执行:cron 与 systemd 的本质差异
6.1 systemd 的 Persistent=true
对于基于 OnCalendar= 的 timer,可以写:
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
Persistent=true 会让 systemd 保存该 timer 上次触发的时间戳。timer 停止期间如果错过了计划时间,重新激活时可能立即激活关联 service。
它解决的是:
timer 不活跃期间错过日历事件
它不是完整的任务队列,也不是“把所有错过的每一天逐个补齐”。
例如:
周一 03:00:机器关机
周四 10:00:机器启动
timer 重新启动后,通常只会产生一次补触发,而不会自动分别执行周一、周二、周三的三次历史任务。若业务要求逐日补偿,必须由任务自己根据持久化状态补齐。
此外,Persistent=true 也不能保证以下情况:
- service 已经被手工禁用;
- timer 文件被删除;
- systemd 数据目录不可写;
- 任务触发了但 service 立即失败;
- 任务执行成功但业务结果未持久化。
“补触发”只说明 systemd 尝试激活 service,不说明业务已经完成。
6.2 Persistent=true 的验证方式
先查看 timer:
systemctl list-timers --all backup.timer
再查看 timer 属性:
systemctl show backup.timer \
-p NextElapseUSecRealtime \
-p LastTriggerUSec \
-p Persistent
查看 service 是否真的运行过:
journalctl -u backup.service --since today
systemctl status backup.service
如果需要测试补触发,不应直接修改生产主机时间。可以在测试虚拟机中:
- 创建测试 timer;
- 启动并记录一次;
- 停止 timer;
- 等待计划时间过去;
- 再启动 timer;
- 观察关联 service 是否被激活。
修改系统时间可能影响数据库、TLS、日志排序、分布式锁和其他业务,应避免在生产环境进行。
七、防重入:为什么“每分钟启动一次”会产生并发
设计划周期为 ,单次任务运行时长为 。
当:
且调度器没有额外延迟时,通常不会因为周期本身产生重叠。
当:
则第一个任务仍在运行时,第二个计划时间已经到达。若调度器每次都直接创建新进程,就会出现并发:
时间轴:
00:00 ├────────────── 任务 A ──────────────┤
00:01 ├────────────── 任务 B ──────────────┤
00:02 ├────────────── 任务 C ...
这会导致:
- 两个进程同时写同一个文件;
- 同一批数据被重复处理;
- 数据库锁竞争;
- 备份任务读取正在变化的源目录;
- API 调用次数突然增加;
- 失败重试和下一次调度叠加。
7.1 cron 中使用 flock
Linux 上常见的防重入方式是使用文件锁:
*/5 * * * * /usr/bin/flock -n 9 /run/lock/app-sync.lock /usr/local/sbin/app-sync.sh
更明确的脚本形式:
#!/usr/bin/env bash
set -Eeuo pipefail
exec 9>/run/lock/app-sync.lock
if ! flock -n 9; then
echo "another instance is running; skip" >&2
exit 75
fi
/usr/local/bin/app-sync
这里的关键步骤是:
exec 9>...打开锁文件,并把文件描述符 9 绑定到它;flock -n 9尝试取得排他锁;-n表示不等待,锁已被占用时立即失败;- 脚本退出或进程异常终止时,内核会释放文件锁。
文件本身通常不需要删除。删除“锁文件”并不能安全地解除一个仍由进程持有的内核锁,反而可能使不同进程对不同 inode 加锁,导致防重入失效。
锁目录需要正确权限。例如 /run/lock 下的文件可能只能由特定用户创建。应预先创建目录或使用 systemd 管理运行时目录,不能让任务第一次运行时才碰运气。
flock 只解决同一台机器、同一个共享内核文件系统上的互斥。多台机器共享 NFS 时,锁语义依赖 NFS 和挂载实现;跨主机任务应使用数据库锁、租约服务或专门的分布式协调机制。
7.2 systemd 为什么通常不会启动同一个 service 的第二个实例
systemd 的 service 单元是有状态的。对于同一个 backup.service:
inactive ──启动──> activating ──成功──> inactive
│
└─失败──> failed
当 timer 再次触发时,如果该 service 已处于运行状态,systemd 不会为同一个 service 单元创建一个普通的第二实例。它不是任务队列,也不会为每次触发自动复制一个新的 service 名称。
这意味着:
Type=oneshot正在运行时,后续触发不会形成同名 service 并发实例;- 任务结束后 service 回到
inactive,下一次触发可以再次运行; - 如果设置了
RemainAfterExit=yes,成功退出后 service 会保持active (exited),后续对同一 service 的启动可能不会再次执行命令。
因此,对周期性的 oneshot 任务通常不要随意设置:
RemainAfterExit=yes
除非确实需要“成功后保持 active”这一状态语义。
不过,不能把 systemd 的单元级行为当成所有防重入需求的替代品:
- 脚本可能自己派生后台进程;
- 任务可能启动了另一个不同名称的 service;
- 多个 timer 可能激活不同 service,但操作同一资源;
- 任务可能在进程退出后仍有外部工作;
- 应用层仍可能需要按资源、租户或业务日期加锁。
对于明确的资源互斥,仍可以在 service 中使用 flock:
# /etc/systemd/system/app-sync.service
[Unit]
Description=Synchronize application data
[Service]
Type=oneshot
User=appsync
ExecStart=/usr/bin/flock -n /run/lock/app-sync.lock /usr/local/sbin/app-sync.sh
八、失败路径:触发成功不等于业务成功
定时任务至少有三个可观察结果:
调度器触发
│
├─ service/进程成功启动
│
├─ 程序正常退出
│
└─ 业务动作成功并持久化
这三个结果不能混淆。
例如 cron 成功创建了 /usr/local/sbin/backup.sh,但脚本因为路径、权限或网络错误立即退出。cron 可能没有能力理解“备份是否完整”。
systemd 可以记录 service 的退出状态:
systemctl show backup.service \
-p ActiveState \
-p SubState \
-p Result \
-p ExecMainStatus
常见诊断命令:
systemctl status backup.timer
systemctl status backup.service
systemctl list-timers --all
journalctl -u backup.service -b --no-pager
journalctl -u backup.service --since "2025-03-11 00:00:00"
如果 service 失败,timer 通常仍然可以等待下一次计划时间;是否自动重试由 service 配置决定,而不是由 OnCalendar 自动保证。
可以显式配置失败后的重启策略,但必须理解它的含义:
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/backup.sh
Restart=on-failure
RestartSec=5min
这会把“任务失败后的重试”交给 service 生命周期处理。若脚本具有副作用,重试前必须确认它是幂等的,否则可能重复扣款、重复发送消息或破坏部分结果。
九、时区设计:调度时区和业务时区必须分层
一个任务可能同时涉及四种时间:
- systemd 或 cron 判断触发的时间;
- Shell 中
date显示的时间; - 应用运行时默认时区;
- 数据库保存和查询所使用的时间。
例如服务器采用 UTC:
date
date -u
应用却以 Asia/Shanghai 显示账单。如果 timer 使用 UTC 的 03:00,它相当于北京时间 11:00;这不是错误,但必须是明确设计的结果。
9.1 以 UTC 作为基础的适用场景
UTC 适合:
- 数据中心跨多个地域;
- 任务按固定间隔运行;
- 日志和事件需要全球排序;
- 不希望夏令时改变触发次数。
例如:
[Timer]
OnCalendar=*-*-* 00:00:00 UTC
它表达的是 UTC 每天零点,而不是主机本地时间零点。
9.2 以业务时区作为规则的适用场景
“每天当地时间 03:00 生成报表”属于业务时区规则。此时应:
- 在 systemd
OnCalendar中明确时区,或; - 用统一 UTC 调度后,由应用按业务时区计算业务日;
- 处理夏令时中不存在或重复的本地时间;
- 记录计划业务日期,而不只记录进程启动时间。
不能仅靠:
[Service]
Environment=TZ=Asia/Shanghai
来改变 timer 的调度时区。这个设置主要影响 service 进程中的环境;timer 在何时触发由 timer 的时间表达式和 systemd 的时区解析决定。
十、cron 和 systemd timer 的选择
10.1 cron 更适合什么
cron 的优点是:
- 配置简单;
- 系统普遍存在;
- 适合简单的用户级周期命令;
- 对旧系统和轻量环境兼容性好。
它的边界是:
- 环境和日志约束较弱;
- 服务生命周期表达能力有限;
- 错过执行通常不补偿;
- 依赖扩展来表达时区、锁和复杂调度;
- 故障、资源限制和安全边界需要额外实现。
10.2 systemd timer 更适合什么
systemd timer 的优势是可以和 service 管理统一起来:
- 明确的 service 用户和权限;
- journal 日志;
- 依赖关系;
TimeoutStartSec等超时;Restart=和失败状态;- 资源控制和安全沙箱;
Persistent=true;- 日历时间和单调时间;
systemctl、list-timers、journalctl统一诊断。
例如可以给任务增加基本资源和权限限制:
[Service]
Type=oneshot
User=backup
Group=backup
ExecStart=/usr/local/sbin/backup.sh
TimeoutStartSec=2h
NoNewPrivileges=yes
PrivateTmp=yes
这些选项不是无条件安全。PrivateTmp、只读文件系统、网络限制等沙箱设置可能破坏脚本对特定路径或设备的访问,必须先验证任务实际依赖。
systemd timer 也不是工作流引擎。它不会天然提供:
- 持久化的多次触发队列;
- 分布式锁;
- 业务级重试状态;
- 复杂依赖图中的数据补偿;
- “每一次历史计划都必须执行”的完整审计。
这些能力仍要由任务程序、数据库或队列系统实现。
十一、一个完整的生产思路:计划、互斥、幂等、补偿
假设需求是:
每天业务时区 03:00 处理前一天数据;机器关机后启动要补做;任务不能并发;失败后可重试。
可以拆成四层:
第一层:systemd timer 负责日历触发
[Timer]
OnCalendar=*-*-* 03:00:00 Asia/Shanghai
Persistent=true
AccuracySec=1min
它负责产生“该尝试处理某个业务日”的触发。
第二层:service 负责进程身份和生命周期
[Service]
Type=oneshot
User=report
ExecStart=/usr/bin/flock -n /run/lock/report.lock /usr/local/sbin/report.sh
TimeoutStartSec=1h
它负责:
- 用低权限用户运行;
- 约束最长运行时间;
- 防止同机并发;
- 把结果接入 journal。
第三层:脚本负责幂等
脚本不能假设“触发一次就只会处理一次”。它应使用业务唯一键或状态表,例如:
report_status(
business_date primary key,
status,
started_at,
finished_at
)
处理前用事务或唯一约束抢占某个 business_date,已成功的日期直接跳过,失败的日期可以安全重试。
第四层:程序负责补偿历史缺口
启动时计算:
待处理日期 = 业务规则要求的日期 - 已成功日期
然后逐个处理缺口,而不是依赖 Persistent=true 产生多次历史触发。
这四层分别解决不同问题:
OnCalendar -> 何时尝试
Persistent -> 停机后是否产生一次补触发
flock -> 同机是否允许并发
业务状态与幂等 -> 重试和历史补偿是否安全
把这四个职责混成一个 cron 表达式,最终通常会得到难以诊断的重复执行或数据遗漏。
十二、排查清单:从“没执行”定位到具体层
12.1 cron 任务没有出现
先确认配置属于哪个用户:
crontab -l
sudo crontab -l
确认 cron 服务状态:
systemctl status cron # Debian/Ubuntu 常见
systemctl status crond # Fedora/RHEL 常见
查看服务日志:
journalctl -u cron
journalctl -u crond
再检查:
- 命令是否使用绝对路径;
- 脚本是否可执行;
- 重定向目标是否可写;
- 脚本的 Shell 解释器是否存在;
/etc/cron.d文件是否包含正确的用户字段;- 配置文件权限和格式是否满足本机实现要求。
12.2 systemd timer 没有触发
systemctl is-enabled backup.timer
systemctl is-active backup.timer
systemctl list-timers --all
systemctl cat backup.timer
重点区分:
- timer 没有启用;
- timer 没有启动;
OnCalendar表达式没有下一次匹配;- timer 已触发但 service 启动失败;
- service 正在运行,新的同名激活没有形成第二实例。
检查表达式:
systemd-analyze calendar '03:00 Asia/Shanghai'
检查 timer 触发和 service 结果:
systemctl show backup.timer -p LastTriggerUSec -p NextElapseUSecRealtime
systemctl show backup.service -p Result -p ExecMainStatus
journalctl -u backup.timer -u backup.service
12.3 任务只执行了一次
优先检查:
RemainAfterExit=yes
如果 Type=oneshot 的 service 成功后保持 active (exited),后续 timer 激活同一 service 时可能不会再次运行命令。周期任务通常应让 oneshot 在命令结束后回到非 active 状态。
然后检查:
- timer 是否仍然 active;
- service 是否持续运行;
- 脚本是否自己进入后台;
- 是否有锁文件导致每次都跳过;
- 下一次触发时间是否因时区或夏令时变化而改变。
12.4 任务重复执行
cron 场景优先检查任务耗时是否大于周期,并添加 flock。
systemd 场景检查:
- 是否是不同 service 名称导致的多个实例;
- 是否有多个 timer 指向不同 service;
- 脚本是否创建后台进程;
- 是否跨主机运行;
- 是否把“重试”误认为“重复调度”。
十三、最终边界
cron 和 systemd timer 都是调度工具,不是业务可靠性本身。
- cron 主要解决“守护进程运行时,匹配某个日历时间后启动命令”;
- systemd timer 解决“用 systemd Unit 管理日历或单调时间,并激活 service”;
- 时区决定计划时间如何映射到实际时间线;
- 防重入决定同一资源是否允许并发;
Persistent=true只能提供有限的停机补触发;- 真正的补偿、幂等、重试和业务审计必须由任务程序或持久化系统承担。
在简单任务中,cron 足够清晰;当任务需要明确的用户、日志、超时、资源限制、失败状态、时区和停机补触发时,systemd timer 通常能提供更完整的生命周期模型。但无论选择哪一种,都应把“计划触发、进程运行、业务完成”分别验证,而不能仅凭配置文件中的时间表达式判断任务可靠。
系列导航与关联阅读
- 系列入口:Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
- 上一篇:Linux 软件包管理:APT、DNF、仓库、签名、依赖与可重复安装
- 下一篇:Linux 块设备与存储:分区、LVM、RAID、挂载和在线扩容
- 延伸:systemd 服务管理:Unit、依赖、重启、资源限制和安全沙箱
- 延伸:Bash 与 Shell 工程实践:展开、引用、管道、错误处理和脚本测试
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论