最可靠方式是用redis-py的set命令原子操作加锁(nx=true, ex=秒数),再配合lua脚本校验value后原子删锁,防止误删;任务耗时不确定时需自动续期,redis不可用必须显式失败而非降级。

用 Redis 实现分布式锁最常用也最可靠
Python 里没有开箱即用的分布式锁原语,threading.Lock 只管单进程,multiprocessing.Lock 仅限本机多进程。跨机器、跨进程协调必须依赖外部存储,Redis 是事实标准——它支持原子命令 SET key value EX seconds NX,能一步完成「设值 + 过期 + 仅当不存在时设置」,这是实现锁安全性的核心。
别自己手写 SETNX + EXPIRE 组合,那会因网络中断或崩溃导致锁永远不释放(死锁)。直接用 redis-py 的 set 方法传参:nx=True、ex=30 即可。
- 安装:
pip install redis - 连接后调用:
redis_client.set("lock:my_job", "worker_abc", nx=True, ex=60),返回True表示抢锁成功 - 释放锁必须用 Lua 脚本校验 value(防误删),不能简单
DEL—— 因为锁可能已被别人续期或超时自动释放
锁释放时必须校验 value 防止误删
多个实例共用一个 Redis key,如果 A 抢到锁但执行慢,超时自动释放;B 接着抢到锁开始执行;此时 A 结束后若直接 DEL lock:my_job,就把 B 的锁删了——后续所有操作都失控。所以释放锁前必须确认这个 key 的 value 确实是自己的标识符。
最稳妥的方式是用 Lua 脚本在 Redis 端原子执行判断+删除:
lua_script = """
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
"""
调用时传入 key 和自己 set 时用的唯一 value(比如 UUID 或 hostname+pid):
redis_client.eval(lua_script, 1, "lock:my_job", "worker_abc")- 返回
1表示删除成功,0表示不是自己的锁,忽略即可
任务执行时间不确定时得加锁续期机制
预设的过期时间(比如 60 秒)如果短于实际任务耗时,锁会提前释放,其他实例趁虚而入,造成并发冲突。但设太长又会导致故障时锁长期滞留。
正确做法是启动一个守护线程/协程,在锁有效期内定期续期(比如每 20 秒把过期时间重设为 60 秒):
- 续期也得校验 value,用同样的 Lua 脚本或
GET + SETEX组合(注意:必须先GET判断仍是自己的锁再续) - 推荐用
redis-lock库(pip install redis-lock),它内置了自动续期和安全释放,比手写更健壮 - 若用
redlock-py,注意它面向多 Redis 实例场景,单节点用redis-lock更轻量
Redis 不可用时脚本必须降级或失败,不能静默跳过锁
分布式锁失效 ≠ 程序不能运行,而是「无法保证互斥」。如果 Redis 连不上,set(..., nx=True) 会抛出 ConnectionError 或 TimeoutError,这时候不能吞掉异常然后继续执行——那等于完全绕过锁逻辑。
必须明确决策:
- 关键任务:直接 raise 异常,让调度系统重试或告警
- 非关键任务:记录 warning 并退出,避免脏数据
- 绝对不要 fallback 到本地文件锁或内存锁——那在分布式下毫无意义
锁本身只是手段,真正重要的是业务一致性。Redis 挂了,靠锁已经救不了,得靠幂等设计、状态检查、补偿事务来兜底。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











