# 38. Redis 中如何高效地实现分布式锁?
# 标准答案
Redis 通过 SET key value NX PX expire_time
实现简单分布式锁,同时 RedLock 算法增强了锁的可靠性。在单节点情况下,使用 SET
命令结合 NX(不存在时设置)+ PX(过期时间) 保证互斥性和自动释放;在多节点情况下,RedLock 通过多个 Redis 实例投票机制提高可用性,确保分布式锁的可靠性。
# 答案解析
在分布式系统中,多实例竞争同一资源时需要分布式锁来避免数据冲突。Redis 作为高性能的内存数据库,提供了一种低延迟的分布式锁实现,主要依赖于SET 命令的原子性和过期机制。
# 1️⃣ 单机版 Redis 分布式锁
# 🔹 方案
使用 SET
命令的原子操作:
SET lock_key unique_value NX PX 10000
1
lock_key
:锁的唯一键,标识资源(如"order:123"
)。unique_value
:唯一标识锁拥有者(如 UUID)。NX
:仅当 key 不存在时设置,防止重复获取锁。PX 10000
:设置过期时间,10 秒后自动释放,防止死锁。
# 🔹 解锁
- 只允许锁的持有者释放锁,防止误删:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
1
2
3
4
5
2
3
4
5
- 为什么不能用
DEL
直接删除?- 如果客户端 A 拿到锁并执行任务,但因网络问题导致锁超时释放,客户端 B 可能已经拿到锁。
- 这时 A 不应该删除锁,否则可能破坏 B 的数据一致性。
- 因此,必须验证 key 是否属于当前客户端,再删除。
🔹 存在的问题
- 锁非强一致:单节点 Redis 可能因主从同步延迟导致锁丢失。
- 锁的超时问题:任务执行时间可能超出锁的 TTL,导致锁提前释放,其他线程误获得锁。
# 2️⃣ RedLock:Redis 分布式锁方案
为了解决单机版 Redis 锁的可靠性问题,RedLock 通过多节点投票,保证分布式环境下的锁安全性。
# 🔹 RedLock 具体步骤
- 同时向多个 Redis 实例(通常是 5 个)请求获取锁。
- 如果超过一半的节点(如 3/5)成功加锁,则认为加锁成功。
- 计算耗时,若获取锁的时间小于锁的超时时间,则加锁成功。
- 业务执行完毕后,依次释放所有 Redis 实例上的锁。
# 🔹 RedLock 的优点
- 高可用性:即使部分 Redis 实例失效,仍可获取锁。
- 防止锁丢失:避免了单节点 Redis 主从同步延迟导致的锁丢失问题。
🔹 示意图
sequenceDiagram
participant Client
participant Redis-1
participant Redis-2
participant Redis-3
Client->>Redis-1: SET lock NX PX 10000
Client->>Redis-2: SET lock NX PX 10000
Client->>Redis-3: SET lock NX PX 10000
Note right of Client: 若 >= 3 个实例成功加锁,则获取锁
Client->>Client: 执行业务逻辑
Client->>Redis-1: DEL lock
Client->>Redis-2: DEL lock
Client->>Redis-3: DEL lock
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
🔹 存在的问题
- RedLock 假设网络可靠,但在网络分区(Split-Brain)下,可能会造成并发问题。
- 性能开销较大,每次加锁都需要多个 Redis 实例交互,增加了网络延迟。
# 3️⃣ 分布式锁的优化方案
# 🔹 续期机制(Watchdog)
如果任务执行时间超过锁的 TTL,默认锁会自动释放,可能导致数据一致性问题。因此,可以定期续期:
- 持有锁的进程每隔一段时间检查锁是否仍属于自己。
- 如果锁仍然存在,则自动续期,防止任务未完成但锁已失效的问题。
- 实现方式
- 使用
Lua
脚本检查并更新 TTL。 - 使用 Redisson 进行自动续期(推荐)。
- 使用
🔹 示例
RLock lock = redissonClient.getLock("myLock");
lock.lock(30, TimeUnit.SECONDS); // 30秒后自动释放,且自动续期
try {
// 执行业务逻辑
} finally {
lock.unlock();
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 4️⃣ 分布式锁与数据库事务对比
对比项 | Redis 分布式锁 | 数据库锁(MySQL 行锁) |
---|---|---|
锁范围 | 进程间分布式锁 | 仅限数据库内 |
性能 | 内存级操作,极快 | 依赖磁盘,性能低 |
死锁处理 | 过期自动释放 | 需手动回滚 |
持久化 | 非持久化,断电丢失 | 持久化 |
适用场景 | 高并发、分布式事务 | 事务冲突控制 |
# 深入追问
🔹 Redis 分布式锁与 Zookeeper 分布式锁的对比? 🔹 RedLock 在网络分区(Split-Brain)情况下是否可靠?如何改进? 🔹 如何避免 Redis 过期时间不够导致任务未完成锁就被释放? 🔹 如何优化 RedLock 以减少网络交互开销?
# 相关面试题
🔹 如何保证 Redis 分布式锁的可重入性?
🔹 为什么 Redis 采用 SET NX 实现锁,而不用 INCR
或 LPUSH
?
🔹 Redis 分布式锁如何与 CAP 原则协调?
🔹 如何解决 Redis 分布式锁的单点故障问题?
# 总结
- 单机版分布式锁 通过
SET NX PX
确保原子性,但不能保证强一致性。 - RedLock 通过多个 Redis 实例投票,提高锁的可靠性,但网络延迟较大。
- 续期机制(Watchdog)解决了任务超时导致的锁释放问题。
- 相比数据库锁,Redis 分布式锁更适合高并发场景,但缺乏持久化能力。
- RedLock 在极端情况下仍可能导致并发问题,需要结合业务需求选择适合的方案。