推荐用 flock 命令实现轻量级互斥锁:在 crontab 中以 /usr/bin/flock -xn /tmp/task.lock -c "cmd" 方式调用,-x 为独占、-n 为失败不等待;Python 脚本内可用 os.open(..., os.O_CREAT | os.O_EXCL) 实现原子文件锁;高频任务需错开触发时间;多机/多容器场景必须使用带唯一 value 和 Lua 安全解锁的 Redis 分布式锁。

脚本在 Crontab 中重复执行,本质是“上一个实例还没结束,下一个周期又被触发”,不是配置写错了,而是缺乏互斥机制。关键不在删掉 cron 条目,而在让脚本或调度层具备识别“正在跑”的能力。
用 flock 命令做外部轻量锁
这是最推荐的通用方案:不改脚本、不依赖语言特性、一行命令接入。
- 把原 crontab 行:
* */2 * * * /usr/bin/python3 /opt/task.py - 改成:
* */2 * * * /usr/bin/flock -xn /tmp/task.lock -c "/usr/bin/python3 /opt/task.py" - -x 表示独占锁,-n 表示拿不到锁立刻退出(不等待),/tmp/task.lock 是锁文件路径,建议用绝对路径
- 锁由系统内核保证原子性,比手动 touch + rm 安全得多,也无需处理进程崩溃残留
Python 脚本内建文件锁(跨平台兼容)
适合需要精细控制、或无法修改 crontab 的场景,核心是用 os.open(..., os.O_CREAT | os.O_EXCL) 原子创建锁文件。
- 脚本开头加:
import os, sysLOCK_FILE = "/tmp/task.pid"try:fd = os.open(LOCK_FILE, os.O_CREAT | os.O_EXCL | os.O_RDWR)os.write(fd, str(os.getpid()).encode())os.close(fd)except OSError:print("Another instance is running. Exiting.")sys.exit(0)- 结尾或异常处理中加
if os.path.exists(LOCK_FILE): os.unlink(LOCK_FILE)
错开执行时间,从源头减少竞争
高频任务(如每分钟)若都卡在 :00 秒触发,哪怕有锁,也会集中争抢,造成瞬时压力。分散执行点更治本。
- 不要全设成
* * * * *,改为0,15,30,45 * * * *或错开秒级偏移(如用sleep $((RANDOM % 30))开头) - 多个同类备份任务,分别设为
10 2 * * *、25 2 * * *、40 2 * * *,避开整点高峰 - 在宝塔面板的任务备注里写明“耗时约8分钟,内存峰值1.2G”,方便后续协同调整
多机/多容器必须用 Redis 分布式锁
单机锁(flock、文件锁、进程检查)在 Docker 多副本、K8s 多 Pod、Webman 多 worker 场景下完全失效——每个实例看到的是自己本地的文件系统或进程表。
- 锁 key 建议含时间维度,例如
task:sync_user:20260615,避免跨天任务互相干扰 - value 必须唯一标识本次执行,推荐
f"{os.getpid()}_{uuid.uuid4().hex}" - 加锁用
SET key value NX EX 3600(NX=不存在才设,EX=过期秒数,设为最大预期耗时+缓冲) - 解锁必须用 Lua 脚本校验 value 再删,防止误删他人锁:
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end

















