如何防止 cron 任务重复(并发)执行?

任务执行时间超过调度间隔会导致并发重叠。标准解法:flock -n 锁文件——上一个没跑完,新的直接跳过。本文给出用法、脚本内置锁与应用内幂等设计。

最佳实践
如何防止 cron 任务重复(并发)执行?封面

标准解法:用 flock——* * * * * /usr/bin/flock -n /tmp/job.lock /path/to/job.sh。它给锁文件加排他锁:上次任务没跑完锁还在,新一轮直接跳过(-n 非阻塞),跑完自动释放。执行时长超过调度间隔的任务都必须加这层保护。

flock 用法

* * * * * /usr/bin/flock -n /tmp/backup.lock /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
  • -n:拿不到锁立即退出(不加则等待,可能越堆越多)
  • 锁文件应位于该账户控制且路径稳定的目录;不要在运行期间删掉/重建锁文件,避免不同进程锁住不同 inode。/run 重启后的目录需重新创建
  • 想记录"被跳过":默认锁冲突退出码为 1,但任务自身也可能返回 1;可用 -E 指定专用冲突码,并与任务退出码约定区分,可包一层记日志;同时记录执行标识、开始和结束时间。持续跳过时,对比任务耗时与调度间隔,检查是否需要优化任务。

脚本内置锁(自带防重入)

不想依赖 crontab 写法,把锁写进脚本:

#!/bin/bash
exec 200>/tmp/myjob.lock          # 打开文件描述符 200
flock -n 200 || { echo "$(date) 上一个实例还在跑,跳过"; exit 1; }

# 正常任务逻辑

好处:怎么调用(cron、手动、其他调度器)都受保护。

其他可选方案

方案 说明
systemd timer OnCalendar 触发的 service 默认不并发(上一个没结束不启新实例)
应用内分布式锁 共享数据库/协调系统锁需正确处理租约、续期、持有者验证及 fencing;不能仅 SETNX 就保证分布式互斥
PID 文件自检 脚本开头查 PID 文件有效性,老派写法

多机场景的特别提醒

使用本机锁文件的 flock 只防本机并发;共享文件系统的锁语义另需验证。同一个任务在多台的 crontab 里都配了(高可用部署常见),要用 Redis/数据库级的分布式锁,或让任务只在 leader 节点跑;也可集中调度;K8s CronJob 的 concurrencyPolicy: Forbid 只约束同一个 CronJob 的 Job,不是跨所有调度器的全局锁。

常见问题(FAQ)

Q:flock 的锁文件要定期清理吗?
A:不用。锁是加在打开的文件句柄上的,文件本身留着无妨;放 /tmp 或 /run 重启自清。

Q:任务卡死了锁永远不释放?
A:当持有该锁的所有打开文件描述符都关闭时才释放;子进程可能继承描述符,父进程退出不一定立即解锁。但任务本身卡死要另外加超时(timeout 300 job.sh)。

Q:想"排队执行"而不是"跳过"?
A:去掉 -n 会等待;-w 限制的是等待锁时间,不是任务运行时长;注意大量堆积的风险,一般跳过+告警更合理。

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台