根本原因是上一个实例未结束而新周期被触发,解决关键是让任务具备“正在运行”识别能力;推荐用flock文件锁,其次脚本内建文件锁、错开执行时间,多机场景须用redis分布式锁。

Cron 任务重复执行,根本原因不是配置写错了,而是上一个实例还没结束,下一个周期又被触发了。解决的关键不是删掉定时条目,而是让任务具备“正在跑”的识别能力。下面几种方法按推荐度和适用场景分层说明。
flock 文件锁(最推荐,通用轻量)
不改脚本、不依赖语言、一行命令接入,由内核保证原子性,比手动 touch/rm 安全得多。
把原 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 是锁文件路径,建议用绝对路径;不存在会自动创建
- 脚本执行完,锁自动释放,崩溃也不会残留(内核级保障)
脚本内建文件锁(适合无法改 crontab 的场景)
Python 示例:用 os.open(..., os.O_CREAT | os.O_EXCL) 原子创建锁文件,跨平台兼容。
在脚本开头加:
import os, sys
LOCK_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(1)
结尾或异常处理中补上清理:
if os.path.exists(LOCK_FILE):
os.unlink(LOCK_FILE)
注意:必须确保锁文件路径可写,且异常路径也要清理,否则进程崩溃后锁残留会导致后续全部失败。
错开执行时间(从源头降低冲突概率)
高频任务(如每分钟)全卡在 :00 秒,即使有锁也会集中争抢,造成瞬时压力。
- 避免全用 * * * * *,改用 0,15,30,45 * * * *
- 多个同类任务分散设置,例如:10 2 * * *、25 2 * * *、40 2 * * *
- 单任务内加随机偏移(慎用):sleep $((RANDOM % 30)); /path/to/script.sh
宝塔等面板里建议备注“耗时约8分钟,内存峰值1.2G”,方便协同调整周期。
多机/多容器必须用 Redis 分布式锁
单机锁(flock、文件锁、进程检查)在 Docker 多副本、K8s 多 Pod、Webman 多 worker 场景下完全失效——每个实例看到的是自己本地的文件系统或进程表。
- 锁 key 建议含时间维度,例如 task:sync_user:20261010,避免跨天干扰
- value 必须唯一标识本次执行,推荐用 UUID 或 PID+时间戳
- 解锁必须用 Lua 脚本原子执行,防止误删他人锁
简单起见,优先考虑业务层合并或调度层统一入口,而不是在每个容器里硬扛分布式锁逻辑。











