RedLock 算法与争议
候选人小张在阿里的三面中,面试官问了一个高难度问题:
"RedLock 了解吗?它是怎么工作的?"
小张说:"Redis 作者提的,用多个 Redis 实例做分布式锁。"面试官追问:"为什么需要多个实例?"
小张说:"单实例不可靠..."面试官继续:"那 RedLock 有没有争议?Martin Kleppmann 怎么批评的?"
小张彻底卡住。
【面试官心理】
这道题我用来区分"会用"和"理解其局限性"的候选人。知道 RedLock 的占 20%,能说出争议的占 5%,能说出替代方案的占 3%。RedLock 是 Redis 分布式锁的进阶话题,涉及到分布式系统的核心问题——时钟漂移和 CAP 理论。
一、RedLock 的设计 🔴
1.1 问题拆解
为什么需要 RedLock?
单机 Redis 分布式锁的问题:
- 主节点宕机,但锁还在从节点
- 从节点晋升为主节点,但锁数据还没同步
- 结果:其他客户端获取了新主节点的锁,但旧锁持有者还在使用
→ 分布式锁失效!
1.2 RedLock 的核心思想
RedLock 用 N 个独立的 Redis 实例,获取锁时在多数实例上成功才算成功:
graph TD
A["客户端: 获取锁"]
B["实例 1: SET NX PX"]
C["实例 2: SET NX PX"]
D["实例 3: SET NX PX"]
E["实例 4: SET NX PX"]
F["实例 5: SET NX PX"]
G{"成功实例 >= 3?"}
H["获取锁成功"]
I["获取锁失败"]
A --> B
A --> C
A --> D
A --> E
A --> F
B --> G
C --> G
D --> G
E --> G
F --> G
G -->|是| H
G -->|否| I
style G fill:#ffd93d
1.3 RedLock 算法流程
public class RedLock {
private List<Jedis> instances; // N 个 Redis 实例
public String lock(String resource, int ttlMs) {
// 1. 计算获取锁的过期时间
long ttl = System.currentTimeMillis() + ttlMs;
// 2. 在 N 个实例上顺序获取锁
List<String> successInstances = new ArrayList<>();
for (Jedis jedis : instances) {
boolean acquired = acquireLock(jedis, resource, ttlMs);
if (acquired) {
successInstances.add(jedis.getClient().getHost());
}
}
// 3. 计算总耗时
long elapsedTime = System.currentTimeMillis() - (ttl - ttlMs);
// 4. 检查是否在有效时间内获取了多数锁
if (successInstances.size() >= N / 2 + 1 &&
elapsedTime < ttlMs) {
return generateLockToken();
} else {
// 5. 失败:释放所有获取到的锁
releaseAllLocks(successInstances, resource);
return null;
}
}
}
【面试官心理】
RedLock 算法看起来很美好——用多数原则保证可靠性。但它真的可靠吗?这就要引入 Martin Kleppmann 的批评了。
二、Martin Kleppmann 的批评 🔴
2.1 核心批评:RedLock 不是有效的分布式锁
2024 年 2 月,分布式系统专家 Martin Kleppmann 发表了一篇著名的批评文章。
2.2 批评一:时钟漂移问题
RedLock 的前提假设:
- 所有 Redis 实例的系统时钟以相同速率前进
- TTL 能准确反映锁的持有时间
实际情况:
- Linux 系统时钟有漂移(每天 ± 秒级)
- 虚拟机迁移会导致时钟跳跃
- 虚拟机暂停(VM pause)会导致时钟"冻结"
graph TD
A["时刻 T: 客户端获取锁 (TTL=10s)"]
B["时刻 T+1s: 实例 2 GC 暂停"]
C["时刻 T+10s: 实例 2 的锁本应过期"]
D["时刻 T+11s: 实例 2 GC 恢复"]
E["时刻 T+11s: 实例 2 锁实际过期"]
F["时刻 T+12s: 客户端 A 认为锁已过期"]
G["时刻 T+12s: 新客户端 B 获取锁 (实例 2)"]
H["时刻 T+12s: 客户端 A 还在使用资源!"]
A --> B --> C --> D --> E --> F --> G --> H
style H fill:#ff6b6b
2.3 批评二:RedLock 的安全性依赖于不可靠的时钟
标准分布式锁的正确性条件:
- 互斥:同一时刻只有一个客户端持有锁
- 死锁避免:锁最终能被释放
RedLock 的问题:
- TTL 不能精确保证锁的持有时间
- 客户端 A 可能认为自己还有锁,但锁已经过期
- 这违反了"互斥"条件
2.4 批评三:RedLock 的性能代价
RedLock 获取锁需要:
- 5 个 Redis 实例
- 串行获取:5 × RTT (假设 RTT = 1ms)
- 总耗时:5ms + 处理时间
单机锁:
- 1 个 Redis 实例
- 1 次 RTT:1ms
2.5 ❌ 错误示范
候选人原话:"RedLock 是 Redis 官方推荐的分布式锁方案,很可靠。"
问题诊断:
- 只看过官方文档,没有看过批评
- 不理解分布式系统中的时钟问题
- 不理解 CAP 理论的实际约束
面试官内心 OS:"这个候选人肯定没有深入研究过 RedLock 的设计缺陷。RedLock 的争议是 Redis 面试中最高级的话题之一,能说清楚的人凤毛麟角。"
三、RedLock 的回应 🟡
3.1 Redis 作者 antirez 的回应
antirez 对 Kleppmann 的批评做出了回应,核心观点:
1. 时钟漂移问题:
- 时钟漂移在 Linux 上通常很小(每天 ±0.5 秒)
- 可以通过 NTP 同步缓解
- GC 暂停是所有语言都有的问题,不只是 Redis
2. RedLock 的设计目标:
- 不是完美的分布式锁,而是"足够好"的锁
- 对于大多数场景,单机 Redis 锁 + Redisson 已经够用
- RedLock 主要用于需要高可靠的场景
3.2 两者的核心分歧
【面试官心理】
这道题我想验证的是候选人的"批判性思维"。知道 RedLock 的占 20%,知道 Martin Kleppmann 批评的占 5%,能说出两者核心分歧的占 3%。分布式系统的设计从来不是非黑即白,理解争议比背结论更重要。
四、工程实践 🟡
4.1 什么时候用 RedLock?
适合用 RedLock:
- 对锁的正确性要求极高
- 有足够的运维能力管理 N 个 Redis 实例
- 可以接受较高的延迟(多次 RTT)
不适合用 RedLock:
- 对延迟敏感(每次锁操作需要 N 次 RTT)
- 运维能力有限
- 锁的正确性要求不是极端严格
4.2 实际工程中的替代方案
方案一:单节点 Redis + Redisson(最常用)
// 对于大多数场景,单机 Redis + Redisson 已经足够
RLock lock = redissonClient.getLock("resource");
lock.lock(30, TimeUnit.SECONDS);
// Redisson 内部实现了 watchdog 自动续期
方案二:Zookeeper 分布式锁(更可靠)
Zookeeper 锁的优势:
- ZAB 协议保证分布式一致性
- 锁的创建和删除是原子操作
- Session 过期自动清理(不需要 watchdog)
- 临时顺序节点天然支持公平锁
// Zookeeper 分布式锁(Curator 实现)
InterProcessMutex lock = new InterProcessMutex(
curatorClient, "/locks/resource"
);
lock.acquire(30, TimeUnit.SECONDS);
try {
// 业务逻辑
} finally {
lock.release();
}
4.3 选择建议
【面试官心理】
这道题我想验证的是候选人的"方案选型能力"。能说出 RedLock 争议的占 10%,能说出替代方案的占 5%。RedLock 不是银弹,Zookeeper 也不是。在实际工程中,选择哪种方案取决于对正确性、性能、运维成本等多个维度的权衡。
五、生产避坑
:::warning ⚠️
生产中的三大翻车点:
-
RedLock 实现错误:很多候选人在面试中能写出 RedLock 的流程,但实际代码实现漏洞百出(如没有正确处理失败回滚、没有考虑时钟漂移)。建议使用 Redisson 或 Curator 的现成实现。
-
锁粒度设计错误:锁的粒度太大导致并发性能差,粒度太小导致锁不住(如按用户锁而不是按订单锁)。正确做法:锁的粒度 = 数据操作的粒度。
-
RedLock vs 主从切换:即使使用了 RedLock,如果某个实例在获取锁后宕机,锁数据丢失,其他客户端可能获取锁。RedLock 只保证了多数节点的情况,但无法处理"锁数据未持久化"的问题。
:::
# 监控 RedLock 各节点的锁获取情况
redis-cli INFO stats | grep -E "sync_full|sync_partial"
# 监控锁竞争情况
# 如果 blocked_clients > 0,说明有大量锁等待
redis-cli INFO clients | grep blocked_clients
:::tip 💡
生产最佳实践:
- 大多数场景:单机 Redis + Redisson 的 watchdog 机制已经足够
- 高可靠场景:使用 Zookeeper 或 etcd
- RedLock:仅在你有足够的 Redis 实例(N >= 5)和运维能力时才考虑
- 不要重复造轮子:使用 Redisson、Curator 等成熟库
- 监控很重要:监控锁等待时间、获取成功率、持有时间等
- 测试不可少:用 chaos engineering 工具测试锁在各种故障场景下的行为
:::
【面试官心理】
RedLock 是 Redis 面试中最高级的话题之一。能说出 RedLock 算法的占 15%,能说出 Martin Kleppmann 批评的占 5%,能理解两者分歧并给出工程建议的占 2%。这道题通常出现在 P7 架构设计面试中。