从 cron 迁到 systemd timer:几个真正有用的开关
定时任务这件事,crontab -e 五分钟就能搞定,所以我用了很多年都没换过。真正让我迁到 systemd timer 的,不是语法好看,而是三次具体的翻车:一次是机器凌晨重启,六点的任务那天干脆没跑;一次是任务里 cd 没写全路径,cron 的 PATH 和我 shell 里的不一样,脚本静默失败;还有一次是任务失败后我翻了半小时 /var/log/syslog,才找到那行藏在系统日志里的 traceback。
systemd timer 不是银弹,但它把「调度」和「运行」分成了两个单元,中间那层多出来的东西,恰好补上了 cron 的几个老洞。
一、OnCalendar 比 cron 表达式好读太多
cron 的 0 6 * * * 还算能看懂,*/15 9-18 * * 1-5 就要在脑子里翻译一遍。systemd 的写法接近自然语言:
[Timer]
OnCalendar=*-*-* 06:00:00
Persistent=true
工作日九点到十八点每十五分钟:OnCalendar=Mon..Fri *-*-* 09..18:00/15:00。语法是有学习成本,但半年后回来看,Mon..Fri 比 1-5 更不容易理解错。可以用 systemd-analyze calendar "Mon..Fri *-*-* 09..18:00/15:00" 直接验证并算出下次触发时间,这一步 cron 没有对应工具,只能靠心算。
二、Persistent=true:错过的任务补跑一次
这是我迁移的第一动力。机器关机、休眠、或者 systemd 重启期间错过的时间点,Persistent=true 会在下次启动后补跑一次。它记录的是「上次触发时间」戳在 /var/lib/systemd/timers/ 下,不是靠推断。
注意是补一次,不是补全部。关机三天不会攒出三次执行——这个语义对备份、报表这类任务刚好,对「每五分钟采集一次指标」就不合适,那种场景缺的数据补不回来,不如不补。
三、RandomizedDelaySec:别让一百台机器同时醒
如果同一个配置铺在多台机器上,06:00:00 会让它们在同一秒打向同一个 API,运气不好就是限流或者雪崩。加一行:
RandomizedDelaySec=300
触发时间会在窗口内随机散开。cron 也能靠 sleep $((RANDOM % 300)) 做到,但那行 sleep 会算进任务耗时,日志里也看不出是延迟还是任务慢。
四、依赖和顺序是白送的
cron 里没法表达「等网络就绪再跑」。只能自己在脚本里轮询,或者祈祷开机顺序。timer 可以写:
[Unit]
After=network-online.target
Wants=network-online.target
真正的价值在于 ExecStartPost:主任务跑完还能挂一个后置动作。我有一台机器上的浏览器服务,重启后需要等 CDP 端口返回 200 再把登录态灌回去,以前是在外面套一层 shell 脚本加 sleep;现在直接:
ExecStartPost=/usr/local/bin/wait-and-restore.sh
失败会记进 systemctl status,不用再去猜那三秒到底等没等到。
五、日志统一到 journalctl
journalctl -u myjob.service --since "today" -o cat
标准输出、标准错误、退出码、启动耗时全在一起,还能按时间窗口过滤。cron 的默认行为是把输出邮寄给 root,机器上没配 MTA 就直接丢弃——这就是「任务好像没跑」最常见的原因。要在 cron 里达到同样效果,得在每个任务后面接 >> /var/log/xxx.log 2>&1,还得自己处理轮转。
六、两个必须知道的坑
任务重叠。 timer 不会阻止上一次还没跑完时下一次又开始。要么在 service 里设 Type=oneshot 之外再加锁:
ExecStart=/usr/bin/flock -n /run/myjob.lock /usr/local/bin/myjob.sh
要么把 AccuracySec 调小、把间隔拉长,从源头避免。
用户级 timer 需要 linger。 用 systemctl --user 部署时,用户退出登录所有 timer 就停了。必须:
loginctl enable-linger $USER
这一条不设,表现出来就是「手动跑好好的,放上去就不动」,非常难查。
七、还要不要用 cron
要。一次性的 at、两行就能写完的小脚本、容器里没有 systemd 的环境,cron 依然更快。判断标准很简单:任务失败后你会不会去查它。会查、需要重试、需要补跑、需要知道耗时——用 timer;只是顺手转个文件、清个临时目录——cron 一行就够了。
迁移的成本主要不在写 unit,而在接受「一个任务变成两个文件」。写完之后你会发现,那些 .timer 和 .service 反而成了最好的文档:它明确写了什么时候跑、跑什么、依赖谁、失败了谁管。
- 点赞
- 收藏
- 关注作者
评论(0)