服务器定时任务别只写 cron:加锁、日志轮转与可观测性实践
为什么只写 cron 不够
很多服务器上的定时任务都是这样开始的:一行 crontab,脚本把结果追加到日志,能跑就行。短时间看不出问题,但运行几个月后,重复执行、日志撑满磁盘、失败了没人知道,这三类问题几乎一定会出现。下面以一台 2 核小服务器上的库存监控脚本为例,把这几件事一次做对。
一、先让任务不会重叠
监控脚本每 10 分钟跑一次,单次耗时偶尔会超过 10 分钟,比如网络超时后连续重试。这时 cron 会再拉起一个进程,两个实例同时写状态文件,就可能出现重复告警。最省事的办法是用 flock 加一把文件锁:
*/10 * * * * /usr/bin/flock -n /tmp/prize-watch.lock /usr/bin/python3 /opt/prize-watch/monitor.py --once >> /var/log/prize-watch/cron.log 2>&1
参数 -n 表示拿不到锁就立刻退出,不排队等待。这样同一时刻只会有一个实例在运行,也不会因为排队把任务越堆越多。
二、日志要能轮转,也要能追查
用 >> 一直追加的日志迟早会把磁盘写满。用 logrotate 按天切分、保留 14 天:
/var/log/prize-watch/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
copytruncate
}
日志内容本身也要带时间戳,否则切分之后就不知道某次检查发生在几点。Python 里用 logging 配置 %(asctime)s 即可,不要只打印结果。
三、把「最后一次成功」写进状态文件
日志文件还在增长,并不代表任务健康。更实用的做法是每次运行结束都覆盖写一个 latest.json,记录检查时间、结果和耗时:
{
"checked_at": "2026-10-24T09:00:03+08:00",
"ok": true,
"duration_ms": 812,
"items": {"a": 0, "b": 0}
}
再配一条最简单的巡检规则:如果 checked_at 超过 30 分钟没有更新,就说明 cron 没跑或者脚本卡住了。这比判断「进程还在不在」可靠得多,因为进程可能活着但已经卡在网络请求上。
四、需要重试时,把重试放在脚本里
cron 本身不会重试。与其把执行频率调得很高,不如在脚本内部对失败的 HTTP 请求做两到三次指数退避,并给整体执行加一个超时。这样既不会堆积进程,也不会因为一次网络抖动就漏掉一整轮检查。重试次数和超时时间都要写进日志,方便事后判断是接口不稳定还是脚本逻辑有问题。
五、什么时候该换成 systemd timer
如果任务之间有依赖顺序、需要直观看到下次执行时间,或者要和其它服务一起管理,systemd timer 会更方便。systemctl list-timers 能直接列出上次和下次触发时间,输出也会自动进入 journald。只是对于「每 10 分钟跑一个脚本」这种简单场景,cron 加 flock 已经足够,没有必要为了迁移而迁移。
小结
定时任务真正难的不是按时触发,而是长期稳定地按时触发。加锁防止重叠、logrotate 防止爆盘、状态文件负责巡检,这三件事做好之后,一台小服务器上的定时任务基本就不需要人天天盯着了。
- 点赞
- 收藏
- 关注作者
评论(0)