systemd timer 还是 crontab:定时任务的五个现实差异

举报
yd_222030558 发表于 2026/11/02 06:25:19 2026/11/02
【摘要】 从错过执行、日志、环境变量、依赖顺序、并发控制五个维度对比,给出选择建议。

先给结论


临时脚本、跑在容器里、需要精确到「每小时第 15 分钟」的,用 crontab。跑在 systemd 系统上、需要补跑、需要看结构化日志、需要依赖其他服务的,用 systemd timer。


下面说具体差异,都是踩过坑之后才注意到的。


差异一:错过执行会不会补


机器在凌晨 3 点重启,而任务定在 2:30。


crontab 的行为是:睡过去就睡过去了,不补。你只能在日志里发现「今天没跑」。


systemd timer 有个 Persistent=true 选项,关机期间错过的任务会在开机后立刻补跑一次:


[Timer]

OnCalendar=*-*-* 02:30:00

Persistent=true

RandomizedDelaySec=300


RandomizedDelaySec 是另一个好东西,最多随机延后 5 分钟触发,避免几十台机器在同一秒同时打向同一个下游。crontab 想做到这点只能自己 sleep $((RANDOM % 300)),写起来很别扭。


差异二:日志去哪了


crontab 的输出默认会发本地邮件,而大多数服务器根本没配 MTA,于是输出直接消失。要么重定向到文件,要么加 MAILTO="" 眼不见为净。


systemd 天然对接 journald:


journalctl -u mytask.service --since today

journalctl -u mytask.service -o json | jq '.MESSAGE'


带时间戳、带优先级、支持结构化字段、支持按时间段过滤,还能配合 journalctl -f 实时看。排障效率的差距是数量级的。


差异三:环境变量不一样


这是 crontab 最经典的坑。cron 的执行环境和你的 shell 环境几乎是两个世界:PATH 通常只有 /usr/bin:/bin,~/.bashrc 不会被加载,所以交互式跑得好好的脚本,在 cron 里报 command not found。


解决办法是在脚本开头自己兜住:


!/usr/bin/env bash

set -euo pipefail

export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"

cd "$(dirname "$0")"


systemd 则是显式声明,没有隐式环境,反而更可控:


Environment=NODE_ENV=production

EnvironmentFile=/etc/mytask/env

WorkingDirectory=/opt/mytask


注意 systemd 的 EnvironmentFile 格式比较朴素:不支持引号包复杂值、不支持变量展开,FOO="$BAR" 会原样传给程序。


差异四:依赖顺序


任务依赖网络就绪,或者依赖某个数据库先起来。


crontab 没有任何依赖概念,最多在脚本里循环 until ping -c1 db; do sleep 2; done。


systemd 可以声明:


[Unit]

After=network-online.target postgresql.service

Wants=network-online.target


After 只保证启动顺序,Wants 才会尝试拉起依赖。语义分得清楚,比在脚本里写重试循环干净。


差异五:并发与资源


同一个任务上一次还没跑完,下一次又来了。


crontab 会直接再起一个进程,两个实例同时操作同一份文件,数据就烂了。常见的土办法是 flock:


*/5 * * * * flock -n /tmp/mytask.lock /opt/mytask/run.sh


systemd 有内置机制,一行配置解决:


[Service]

Type=oneshot

ExecStart=/opt/mytask/run.sh

TimeoutStartSec=600


oneshot 类型的 service 在启动完成前不会重复启动,timer 触发时会自动跳过。另外 TimeoutStartSec 能防止任务卡死后永远挂着,配合 Restart=on-failure 还能自动重试。


一个完整的 systemd 例子


/etc/systemd/system/mytask.service:


[Unit]

Description=Daily data sync

After=network-online.target

Wants=network-online.target


[Service]

Type=oneshot

User=app

WorkingDirectory=/opt/mytask

EnvironmentFile=/etc/mytask/env

ExecStart=/usr/bin/node dist/index.js

TimeoutStartSec=1800


/etc/systemd/system/mytask.timer:


[Unit]

Description=Run data sync daily at 06:00


[Timer]

OnCalendar=*-*-* 06:00:00

Persistent=true

RandomizedDelaySec=120

Unit=mytask.service


[Install]

WantedBy=timers.target


启用:


sudo systemctl daemon-reload

sudo systemctl enable --now mytask.timer

systemctl list-timers --all # 确认下次触发时间

systemctl start mytask.service # 手动跑一次验证


systemctl list-timers 输出的 NEXT 列非常实用,能直接确认时区算得对不对。如果服务器是 UTC 而你以为它是东八区,OnCalendar 会以 UTC 解释,实际执行时间差 8 小时——这个错误每年都有人犯。


怎么选

· 只有一台机器、任务简单、已经在用 cron 管理一堆东西 → 继续用 cron,加 flock 和日志重定向就够。

· 跑在 systemd 系统上、任务重要、需要补跑和结构化日志 → 用 timer,多花的配置时间会还回来。

· 跑在容器里 → 两个都不用,容器编排本身就有 cron 语义(K8s 的 CronJob),或者干脆用外部调度。

· 云函数 → 用平台自己的定时触发器,别在函数里再套一层 cron。


最后一句:不管用哪个,都要有「任务没跑」的告警。定时任务最可怕的失败方式不是报错,而是安静地什么都没做,两周后才发现数据一直没更新。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。