# 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 秒后自动释放,防止死锁。

# 🔹 解锁

  1. 只允许锁的持有者释放锁,防止误删:
if redis.call("GET", KEYS[1]) == ARGV[1] then
    return redis.call("DEL", KEYS[1])
else
    return 0
end
1
2
3
4
5
  1. 为什么不能用 DEL 直接删除?
    • 如果客户端 A 拿到锁并执行任务,但因网络问题导致锁超时释放,客户端 B 可能已经拿到锁。
    • 这时 A 不应该删除锁,否则可能破坏 B 的数据一致性。
    • 因此,必须验证 key 是否属于当前客户端,再删除。

🔹 存在的问题

  1. 锁非强一致:单节点 Redis 可能因主从同步延迟导致锁丢失。
  2. 锁的超时问题:任务执行时间可能超出锁的 TTL,导致锁提前释放,其他线程误获得锁。

# 2️⃣ RedLock:Redis 分布式锁方案

为了解决单机版 Redis 锁的可靠性问题,RedLock 通过多节点投票,保证分布式环境下的锁安全性。

# 🔹 RedLock 具体步骤

  1. 同时向多个 Redis 实例(通常是 5 个)请求获取锁
  2. 如果超过一半的节点(如 3/5)成功加锁,则认为加锁成功
  3. 计算耗时,若获取锁的时间小于锁的超时时间,则加锁成功
  4. 业务执行完毕后,依次释放所有 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

🔹 存在的问题

  1. RedLock 假设网络可靠,但在网络分区(Split-Brain)下,可能会造成并发问题。
  2. 性能开销较大,每次加锁都需要多个 Redis 实例交互,增加了网络延迟。

# 3️⃣ 分布式锁的优化方案

# 🔹 续期机制(Watchdog)

如果任务执行时间超过锁的 TTL,默认锁会自动释放,可能导致数据一致性问题。因此,可以定期续期

  1. 持有锁的进程每隔一段时间检查锁是否仍属于自己
  2. 如果锁仍然存在,则自动续期,防止任务未完成但锁已失效的问题。
  3. 实现方式
    • 使用 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

# 4️⃣ 分布式锁与数据库事务对比

对比项 Redis 分布式锁 数据库锁(MySQL 行锁)
锁范围 进程间分布式锁 仅限数据库内
性能 内存级操作,极快 依赖磁盘,性能低
死锁处理 过期自动释放 需手动回滚
持久化 非持久化,断电丢失 持久化
适用场景 高并发、分布式事务 事务冲突控制

# 深入追问

🔹 Redis 分布式锁与 Zookeeper 分布式锁的对比? 🔹 RedLock 在网络分区(Split-Brain)情况下是否可靠?如何改进? 🔹 如何避免 Redis 过期时间不够导致任务未完成锁就被释放? 🔹 如何优化 RedLock 以减少网络交互开销?

# 相关面试题

🔹 如何保证 Redis 分布式锁的可重入性? 🔹 为什么 Redis 采用 SET NX 实现锁,而不用 INCRLPUSH? 🔹 Redis 分布式锁如何与 CAP 原则协调? 🔹 如何解决 Redis 分布式锁的单点故障问题?

# 总结

  1. 单机版分布式锁 通过 SET NX PX 确保原子性,但不能保证强一致性。
  2. RedLock 通过多个 Redis 实例投票,提高锁的可靠性,但网络延迟较大。
  3. 续期机制(Watchdog)解决了任务超时导致的锁释放问题。
  4. 相比数据库锁,Redis 分布式锁更适合高并发场景,但缺乏持久化能力。
  5. RedLock 在极端情况下仍可能导致并发问题,需要结合业务需求选择适合的方案。