Python 分布式锁的多种实现对比

1次阅读

redis分布式锁不能只用setnx,因其无过期机制易致死锁;应使用set key value ex seconds nx原子命令,并确保value唯一、释放时校验value,避免误删。

Python 分布式锁的多种实现对比

Redis 实现分布式锁为什么不能只用 SETNX

因为 SETNX 本身不带过期时间,一旦客户端崩溃或网络中断,锁就永远卡住。实际中几乎没人单独用它。

正确做法是用 SET key value EX seconds NX 一条命令完成“设置+过期+原子性”,避免竞态。Redis 2.6.12 之后才支持这个语法,老版本得靠 lua 脚本兜底。

  • 值(value)必须唯一(比如 UUID),后续释放锁时要校验,防止误删别人持有的锁
  • 过期时间不能太短(任务执行时间波动大时会提前释放),也不能太长(故障恢复慢)
  • 别用 DEL 直接删锁——得用 Lua 脚本先比对 value 再删,否则可能删错

redis-pyLock 类要注意什么

封装了上述逻辑,但默认行为在某些场景下很危险:比如没设 blocking_timeoutacquire() 会无限等待;又比如没传 thread_local=False线程里可能锁失效。

常见错误现象:Lock.acquire() 卡死、多个进程同时拿到锁、锁自动续期失败。

立即学习Python免费学习笔记(深入)”;

  • 务必显式设置 timeout(锁过期时间)和 blocking_timeout(阻塞等待上限)
  • 跨进程/服务部署时,关掉 thread_local=True(默认值),否则每个线程有独立锁状态
  • 如果任务可能超时,得自己实现续期(lock.extend()),但注意它不是原子的,高并发下可能续不上

zookeeper 方案在 python 里怎么避免 ConnectionLossException

zk 的临时顺序节点天生适合做分布式锁,但 Python 客户端(如 kazoo)对连接抖动敏感,一断开就抛 ConnectionLossException,且不会自动回滚锁状态。

这不是配置问题,是 ZooKeeper 自身模型决定的:客户端断连后,临时节点立刻消失,但你的业务代码可能还在跑,造成“锁没了但活儿没停”的状态错乱。

  • 所有锁操作必须包裹在 with client.handler 或手动处理异常,检测到断连就主动放弃或重试
  • 不要依赖 KazooClient.Lock 的自动重连——它只恢复连接,不恢复锁上下文
  • 如果业务允许,优先用 EnsurePath + 自定义节点监听代替原生 Lock,控制力更强

本地锁 + Redis 锁组合使用时,threading.Lock 放哪儿最安全

很多人想“先用本地锁防同一进程内竞争,再用 Redis 锁防跨进程”,结果把 threading.Lock 放在 Redis 锁获取之前,反而制造了单点瓶颈——所有线程抢本地锁,Redis 锁成了摆设。

真正合理的顺序是:先快速尝试拿 Redis 锁(带短 timeout),失败就退;成功后再用 threading.Lock 做进程内精细同步,比如保护某个共享缓存变量。

  • threading.Lock 只该用在 Redis 锁已确认持有之后,且作用域严格限定在当前进程内
  • 别在 acquire() 外层套本地锁——这会让分布式锁的并发优势归零
  • 如果只是防重复提交,通常单 Redis 锁就够了,加本地锁反而增加死锁风险

复杂点在于锁的生命周期管理:Redis 锁的过期、客户端异常退出、网络分区、时钟漂移……这些都会让“谁持有了锁”变得不确定。别指望某一种实现能全自动兜底。

text=ZqhQzanResources