记录分布式锁的常见实现方案、各自优缺点及选型思考。

为什么需要分布式锁

单机环境下可以用语言内置锁(如 Java synchronized、Go Mutex)保证临界区互斥。多实例部署后,进程分布在不同机器上,需要跨进程的协调机制——分布式锁。

典型场景:库存扣减、定时任务防重复执行、配置热更新互斥。

三种主流实现

1. Redis 分布式锁

最简单的实现是 SET key value NX PX:

1
2
3
4
5
# 加锁(key=资源标识, value=唯一标识, NX=不存在才设置, PX=过期时间)
SET lock:order:123 uuid-xxx NX PX 30000

# 解锁(Lua 脚本保证原子性:校验 value 后删除)
EVAL "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock:order:123 uuid-xxx

优点:性能高,实现简单。

风险:

  • 锁过期但业务未完成 → 需要看门狗续期(如 Redisson)。
  • 主从切换丢锁 → Redlock 算法(多节点半数以上加锁成功)。

2. Zookeeper 分布式锁

基于临时顺序节点实现:

1. 创建临时顺序节点 /lock/node-0001
2. 获取 /lock 下所有子节点,判断自己是否序号最小
3. 如果最小 → 获得锁
4. 如果不是 → 监听前一个节点的删除事件
5. 前一个节点删除 → 重新检查自己是否最小
6. 业务完成 → 删除自己的节点(释放锁)

优点:客户端断开自动释放(临时节点特性),公平锁,可靠性高。

缺点:性能低于 Redis,Zookeeper 集群运维复杂。

3. etcd 分布式锁

基于 Lease + Compare-And-Swap 实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 伪代码
lease := client.Grant(ctx, 30)  // 申请 30s 租约
txn := client.Txn(ctx).
    If(mod_revision == 0).       // key 不存在
    Then(Put(key, value, WithLease(lease))).
    Else(Get(key))
result := txn.Commit()
if result.Succeeded {
    // 加锁成功,启动续期 keepalive
}

优点:Raft 强一致性,Kubernetes 原生组件,可靠性高。

缺点:API 相对复杂,性能不如 Redis。

对比总结

维度 Redis Zookeeper etcd
性能 最高 中等 中等
可靠性 中(需 Redlock 增强) 高 高
公平性 非公平 公平 可实现公平
运维复杂度 低 高 中
适用场景 高并发、容忍偶尔失效 强一致性要求高 K8s 生态集成

实践建议

  1. 大多数场景 Redis 分布式锁(配合 Redisson 看门狗)已足够。
  2. 对正确性要求极高的金融场景,优先选 Zookeeper 或 etcd。
  3. 锁粒度尽量小,持有时间尽量短。
  4. 加锁后必须有超时保护和异常释放机制。

小结

分布式锁没有银弹,选型需在性能与可靠性之间权衡。后续会记录 Redisson 看门狗源码分析与 Redlock 争议的思考。