Distributed Locks Implemented Right

Redis SETNX and Redlock, database locks, Zookeeper/etcd locks, watchdog renewal and common pitfalls.

AdvancedUpdated 2026-10-2966K views

What a Distributed Lock Must Guarantee

  • Mutual exclusion: one holder at a time.否则没有意义
  • No deadlock: a crash releases the lock (expiry / session).
  • Fairness (optional): FIFO under contention.
  • Crash-safe for both holder and locker: TTL plus renewal take care of the failure modes.

Redis Lock and its Traps

# 单实例正确姿势:SET key value NX PX <ttl>
#   value = 唯一标识(UUID),解锁时必须先比对再删(Lua 保证原子)

加锁:
  token = uuid()
  SET lock:order:7 $token NX PX 10000

续期(看门狗):
  后台线程在 TTL/3 时检查持有者仍是自己则续期
  Redisson 的 watchdog 默认 30s ttl、10s 续期

解锁(Lua 原子):
  if redis.call('get', KEYS[1]) == ARGV[1] then
      return redis.call('del', KEYS[1])
  else return 0 end

常见错误:
  ✗ SETNX + EXPIRE 两步(中途崩溃锁不过期)
  ✗ 解锁直接 DEL(删了别人的锁)
  ✗ 业务超过 TTL 未续期 → 锁过期被他人抢到 → 并发
  ✗ 把 Redis 锁当强一致锁(主从切换瞬间可能双持)

Redlock

Redlock:多实例多数派(N≥5,加锁 ≥ N/2+1 成功且耗时 < TTL)

  对 5 个独立 Redis 同时申请:
    成功数 ≥ 3 且剩余有效时间足够 → 锁定成功
  任意一步失败 → 全部解锁重试

争议(Martin Kleppmann):
  依赖系统的时钟同步;GC 期 pause、时钟跳变可能双持
  作者(antirez)反驳:实际工程中足够好

结论(实用主义):
  对「防止重复操作」这类业务级互斥 → Redis 锁 + 看门狗,足够
  对「钱、库存等强正确性」→ 数据库唯一键 / ZK / etcd 类 CP 系统
If losing a lock translates into money lost, do not use Redis locks: use a CP system (Zookeeper, etcd) or a database constraint.

Database and CP System Locks

数据库方案(最简单、可靠):
  唯一键约束表:INSERT lock(name, owner, expire_at)
     借助唯一索引实现互斥,崩溃靠 expire_at 过期
  SELECT ... FOR UPDATE:事务内行锁,事务结束即释放

Zookeeper / etcd:
  创建临时顺序节点 / lease 会话
  会话断开自动删除(无死锁安全)
  顺序节点 = 天然公平队列
  etcd lease(TTL) + txn 原子比较版本号

对比:获取锁后干完活就走用 CP 锁;长时间持锁用续期;
  极高吞吐 + 容忍极小风险用 Redis;正确性第一用 ZK/etcd

Friends & Partners