分布式锁选型指南:Redis vs ZooKeeper vs etcd
没有完美的分布式锁,只有合适的取舍
分布式锁的本质要求
分布式锁必须满足三个条件:
- 互斥性:同一时刻只有一个客户端持有锁
- 不死锁:持有锁的客户端崩溃后,锁必须最终被释放
- 容错性:部分节点故障时,锁服务仍可用
没有任何方案能在 CAP 约束下完美满足所有条件——这是选型的根本出发点。
Redis 方案:高性能,弱一致
单节点 SET NX PX:
SET lock_key unique_value NX PX 30000
NX(不存在才设置)+ PX(毫秒过期)是原子操作,安全性远高于早期的 SETNX + EXPIRE 两步操作。
释放必须用 Lua 脚本,防止误删他人的锁:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
end
return 0
单节点的问题:主从切换时,锁数据可能还未同步到从节点,导致同一把锁被两个客户端同时获得。
Redlock 试图解决这个问题:向 5 个独立 Redis 节点申请锁,获得多数(≥3)则成功。但 Martin Kleppmann 指出 Redlock 在时钟漂移和 GC pause 场景下仍有问题。
Redis 锁适用场景:对锁的安全性要求不极端苛刻,但对性能要求极高(10万+ QPS)的业务场景,如限流、防重复提交。
ZooKeeper 方案:强一致,性能适中
ZooKeeper 基于 ZAB 协议(类 Paxos)保证强一致性。实现分布式锁的经典方式是临时顺序节点:
/locks/resource_
/locks/resource_0000000001 ← 序号最小者持有锁
/locks/resource_0000000002 ← 监听前一个节点
/locks/resource_0000000003
客户端崩溃时,临时节点自动删除,天然解决死锁。Watch 机制实现高效的等待队列,避免惊群效应。
适用场景:对锁安全性要求极高的金融场景。Curator 框架封装了完整实现。
局限:写性能上限约 10k/s,且存在 ZooKeeper 本身的运维复杂度。
etcd 方案:现代化强一致
etcd 基于 Raft 协议,API 比 ZooKeeper 更简洁:
etcdctl lease grant 30 # 创建 30 秒租约
etcdctl put lock_key value --lease=<lease_id> # 带租约写入
Lease 机制对应 ZooKeeper 的临时节点,keepalive 自动续租。etcd 还提供了原生的 v3/concurrency 包封装了锁语义。
选型决策矩阵
| 维度 | Redis (单节点) | Redlock | ZooKeeper | etcd | |------|--------------|---------|-----------|------| | 一致性 | 弱 | 争议 | 强 | 强 | | 性能 | 极高 | 高 | 中 | 中高 | | 运维复杂度 | 低 | 中 | 高 | 中 | | 死锁安全 | TTL 保证 | TTL 保证 | 会话保证 | 租约保证 | | 推荐场景 | 限流/防重 | 慎用 | 金融核心 | 云原生基础设施 |