用 systemd timer 替代 cron:让定时任务更可控

举报
yd_292915714 发表于 2026/11/04 07:31:52 2026/11/04
【摘要】 平时写定时任务,很多人第一反应是 crontab -e。cron 简单直接,但也有几个绕不开的短板:最小粒度只到分钟、没有失败重试、日志靠重定向、任务之间无法声明依赖,出问题时只能翻 /var/log/cron 猜。如果机器上是 syste

平时写定时任务,很多人第一反应是 `crontab -e`。cron 简单直接,但也有几个绕不开的短板:最小粒度只到分钟、没有失败重试、日志靠重定向、任务之间无法声明依赖,出问题时只能翻 `/var/log/cron` 猜。如果机器上是 systemd 系统(现在绝大多数发行版都是),用 systemd timer 往往更省心。

## 一个最小可用的例子

假设要每 10 分钟跑一次同步脚本,传统写法是一行 crontab:

```bash
*/10 * * * * /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1
```

换成 systemd,需要两个文件。先写服务单元 `/etc/systemd/system/sync.service`:

```ini
[Unit]
Description=Sync data to remote
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/sync.sh
User=app
```

再写定时器 `/etc/systemd/system/sync.timer`:

```ini
[Unit]
Description=Run sync every 10 minutes

[Timer]
OnBootSec=2min
OnUnitActiveSec=10min
AccuracySec=30s
Persistent=true

[Install]
WantedBy=timers.target
```

启用只需要一条命令:

```bash
systemctl daemon-reload
systemctl enable --now sync.timer
```

之后用 `systemctl list-timers` 就能看到下次触发时间、上次触发时间、还剩多久,比 cron 的“黑盒”状态清楚得多。

## systemd timer 好在哪

**第一,日志统一进 journal。** 脚本里的 `echo`、标准错误、退出码都会被 systemd 收走,排查时 `journalctl -u sync.service -n 100` 直接看,不用自己在脚本里重定向,也不用担心日志文件把磁盘写满(journal 有轮转策略)。

**第二,可以声明依赖和前置条件。** `After=network-online.target` 保证网络就绪再跑,`ConditionPathExists=/data/ready` 可以在条件不满足时直接跳过而不报错。cron 里这些都得写进脚本。

**第三,失败可见、可重试。** 服务单元里加 `Restart=on-failure` 和 `RestartSec=30`,失败后自动重试;`OnFailure=` 还能挂一个告警单元,失败时发通知。cron 完全做不到这点。

**第四,时间语义更丰富。** 除了固定间隔,还支持 `OnCalendar=Mon..Fri 09:00` 这种人类可读的日历表达式,`Persistent=true` 表示如果关机错过了触发点,开机后补跑一次——cron 的 `@reboot` 只能做到“开机就跑”,没法判断是否漏跑。

## 几个容易踩的坑

**环境变量不一样。** systemd 服务默认不加载 `/etc/profile` 和 `~/.bashrc`,`PATH` 只有 `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin`。脚本里如果调用了装在非标准路径的工具,要么写绝对路径,要么在服务单元里显式设置:

```ini
Environment=PATH=/usr/local/bin:/usr/bin:/bin
Environment=SYNC_TOKEN=xxxx
```

**`Type=oneshot` 不能少。** 定时任务通常是跑完就退出的脚本,必须声明 `Type=oneshot`。如果写成默认的 `Type=simple`,systemd 会认为服务一直在运行,下一次触发时会被判定为“上一次还没结束”而跳过。

**不要用 `OnUnitActiveSec` 加 `OnCalendar` 混搭。** 两者同时写会取并集,触发频率可能远超预期。固定间隔就用 `OnUnitActiveSec`,固定时刻就用 `OnCalendar`,二选一。

**改完单元文件必须 `daemon-reload`。** 否则 systemd 用的还是旧定义,改了等于没改,这个坑几乎每个人都踩过一次。

## 什么时候还是该用 cron

systemd timer 不是万能的。如果是纯用户级任务、不想碰 root 权限,`systemctl --user` 虽然可用,但需要用户登录会话保持存活(要开 `loginctl enable-linger`)。另外在容器里,systemd 经常根本没跑,这时候 cron 或者应用内调度才是正解。

我的习惯是:系统级、需要可观测性和失败处理的定时任务用 systemd timer;临时的一次性提醒、用户目录下的轻量脚本继续用 cron。两者并不冲突,选对场景就好。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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