
最直接有效的方式,是用 flock -xn 在 crontab 条目里包裹命令,靠文件锁实现“有锁才跑、没锁跳过”,不排队、不堆积、不残留。
用 flock 包裹命令(推荐首选)
在 crontab 中不直接调用脚本,而是用 flock 控制执行入口:
- -x:申请独占锁,确保同一时间最多一个实例持有
- -n:非阻塞模式——抢不到锁立刻退出,不等待也不重试
- -c:后接完整命令字符串,必须用单引号或双引号包住,包括重定向和错误处理
- 锁文件用绝对路径,例如
/var/run/backup.lock或/opt/myapp/lock.sync.lock
示例(每 10 分钟执行一次,但脚本可能耗时 15 分钟):
*/10 * * * * flock -xn /var/run/backup.lock -c '/usr/local/bin/do_backup.sh >> /var/log/backup.log 2>&1'
锁文件位置要稳,别被系统清掉
锁文件不能放在易失路径,否则可能被误删导致“假空闲”:
-
/tmp/下的锁文件在部分发行版(如使用 tmpfs 的 Ubuntu/Debian)会被定时清理,不建议使用 -
/var/run/是标准运行时目录,重启后清空但日常稳定;若需持久化,可放在项目自有目录下(如/opt/app/.lock) - flock 会自动创建锁文件,无需提前 touch,也不用手动管理文件存在性
加点日志,让跳过也“看得见”
单纯跳过执行容易让人误以为 cron 没触发,建议补一句失败记录:
- 在命令末尾加
|| echo "$(date): skipped — already running" >> /var/log/myjob.log - 这样每次因锁被拒,都会留下明确时间戳,方便排查“不是没调度,是被拦住了”
别踩这些常见坑
有些做法看似简单,实际隐患大:
- 脚本内自己
touch/rm锁文件:进程异常退出时锁残留,后续永远卡死 - 用
pgrep或pidof查进程名:存在毫秒级竞态窗口,且命令行参数变化时匹配失效 - 加
sleep或重试逻辑:违背防重初衷,反而造成任务堆积和资源争抢 - 锁文件写死 value 或 key 不带时间维度(尤其 Webman 多实例场景):跨周期或跨容器锁失效











