分布式锁: Redisson红锁(RedLock)原理与实现细节

[复制链接]
发表于 2025-8-28 00:27:44 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×



分布式锁是分布式系统的核心基础办法,但 单节点 Redis 锁在高可用场景下存在致命缺陷:当 Redis 主节点宕机时,从节点大概因异步复制未完成而丢失锁信息,导致多个客户端同时持有锁。为此,Redis 作者 Antirez 提出了 RedLock 算法,旨在通过多节点协作提升分布式锁的可靠性。本文将深入剖析 Redisson 中 RedLock 的实现原理、技术争议与最佳实践。

一、单节点 Redis 锁的范围性

1. 主从架构下的锁丢失问题

假设以下场景:

  • 客户端 A 在 Redis 主节点成功获取锁。
  • 主节点宕机,锁尚未同步到从节点。
  • 从节点晋升为新主节点,此时客户端 B 也能获取同一把锁。
  • 结果:客户端 A 和 B 同时持有锁,违反互斥性。
2. 异步复制的风险

Redis 主从复制默认异步,锁的写入大概在故障切换后丢失。

二、RedLock 算法核心思想

RedLock 的核心是通过 多个独立的 Redis 节点(至少 5 个)协作实现分布式锁。算法步骤如下:
1. 加锁流程


  • 向所有节点发起加锁请求
    客户端依次向 N 个独立 Redis 节点发送加锁下令:
    1. SET lock_key <unique_value> NX PX <expire_time>
    复制代码

    • 使用相同的 Key 和唯一值(如 UUID + 线程ID)。
    • 设置公道的过期时间(通常为 10-30 秒)。

  • 盘算有效锁数目
    客户端统计成功得到锁的节点数。若 多数节点(≥ N/2 +1) 返回成功,则以为加锁成功。
    示例:N=5 时,至少须要 3 个节点成功。
  • 盘算锁的实际持有时间
    锁的最终有效时间 = 初始过期时间 - 加锁过程耗时。

    • 若实际持有时间过短(如剩余时间 < 业务实行时间),需立即释放锁。

2. 释放锁流程

向所有节点发送释放锁的 Lua 脚本(无论是否加锁成功):
  1. if redis.call("get",KEYS[1]) == ARGV[1] then
  2.     return redis.call("del",KEYS[1])
  3. else
  4.     return 0
  5. end
复制代码

三、Redisson 中 RedLock 的实现

1. 设置多节点 Redisson 客户端

需为每个独立 Redis 节点创建 RedissonClient 实例:
  1. Config config1 = new Config();
  2. config1.useSingleServer().setAddress("redis://node1:6379");
  3. RedissonClient client1 = Redisson.create(config1);
  4. Config config2 = new Config();
  5. config2.useSingleServer().setAddress("redis://node2:6379");
  6. RedissonClient client2 = Redisson.create(config2);
  7. // ... 创建其他节点客户端
  8. RLock lock1 = client1.getLock("myLock");
  9. RLock lock2 = client2.getLock("myLock");
  10. RLock lock3 = client3.getLock("myLock");
  11. RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
复制代码
2. 加锁的底层逻辑

调用 redLock.lock() 时,Redisson 内部实行以下步骤:

  • 向所有节点并行发起加锁请求
    使用异步线程池同时向 N 个节点发送加锁下令。
  • 统计成功加锁的节点数

    • 若成功节点数 ≥ 多数(如 5 节点需 ≥3),则加锁成功。
    • 否则,向所有节点发送解锁下令,并抛出 LockException。

  • 启动看门狗续期(可选)
    若未指定 leaseTime,Redisson 会为所有成功节点启动看门狗线程,定期续期锁。
3. 解锁流程

调用 redLock.unlock() 时:

  • 向所有节点发送解锁下令
    纵然某些节点加锁失败,也需实验解锁。
  • 处理部分节点失败
    若某些节点解锁失败(如网络问题),Redisson 会记载日志日志,但不会重试(需业务层处理)。

四、RedLock 的技术争议与应对策略

1. 争议点

1.1 时钟跳跃问题



  • 场景:若某 Redis 节点发生时钟跳跃(如 NTP 同步导致时间回拨),大概导致锁提前过期。
  • Redisson 的应对:
    默认依赖 Redis 服务器的系统时间,发起禁用自动时钟同步(或在物理机情况运行)。
1.2 网络延迟与 GC 停顿



  • 场景:客户端因 GC 停顿或网络延迟,误判锁已释放。
  • 解决思绪:

    • 锁过期时间应宏大于业务最大实行时间(如设置 30 秒,业务实行时间 ≤10 秒)。
    • 使用唯一 Token(unique_value)确保只有锁持有者能释放锁。

1.3 算法安全性争议

Martin Kleppmann 在 How to do distributed locking 中指出:


  • RedLock 依赖「系统模子假设」(如无时钟跳跃、无长时间 GC),在异步模子下无法包管绝对安全。
  • 推荐使用基于 ZooKeeper/etcd 的 CAS 操作 替换。
Antirez 的回应 Is Redlock safe?:


  • RedLock 在 实践中的大多数场景 下富足安全,但需权衡场景需求。
2. 使用发起



  • 适用场景:对锁的可靠性要求高,可容忍极低概率的锁失效(如非金融场景)。
  • 规避方案:

    • 结合业务幂等性 + 状态机,纵然锁失效也能包管最终一致性。
    • 使用 Fencing Token(递增令牌)防止过期锁操作资源(需存储层支持)。


五、RedLock 性能优化

1. 节点数目选择



  • 发起节点数:5 或 7(容错本领与性能的平衡)。
  • 容错本领公式:允许宕机节点数 = (N-1)/2。
2. 超时时间设置



  • 加锁超时:发起 50-200ms(避免长时间壅闭)。
  • 锁过期时间:业务最大实行时间的 2-3 倍。
3. 异步加锁优化

使用 tryLockAsync() 实现非壅闭加锁:
  1. RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
  2. RFuture<Boolean> future = redLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
  3. future.whenComplete((res, ex) -> {
  4.     if (res) {
  5.         // 加锁成功
  6.     }
  7. });
复制代码

六、对比其他方案

方案优点缺点适用场景Redis 单节点锁高性能、简单主从切换大概丢锁非关键业务、低并发RedLock高可靠性(多节点容错)性能较低、实现复杂高可靠性要求ZooKeeper 锁CP 模子、强一致性性能差、依赖 ZK 集群金融、政务系统etcd 锁高并发、强一致性(Raft 协议)功能较简单Kubernetes 生态、高并发
七、总结

Redisson 的 RedLock 实现通过多节点协作,明显提升了分布式锁的可靠性,但其复杂性、性能消耗和潜在风险(如时钟问题)需谨慎评估。技术选型发起:


  • 通例场景:优先使用单节点 Redis 锁(结合业务幂等性)。
  • 高可靠场景:评估 RedLock 或转向 ZooKeeper/etcd。
  • 混合方案:对关键资源使用 RedLock,非关键资源使用单节点锁。
最终,分布式锁的可靠性不仅依赖中间件,还需结合业务层的容错设计(如事务补偿、异步校对),才能构建结实的分布式系统。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表