马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
分布式锁是分布式系统的核心基础办法,但 单节点 Redis 锁在高可用场景下存在致命缺陷:当 Redis 主节点宕机时,从节点大概因异步复制未完成而丢失锁信息,导致多个客户端同时持有锁。为此,Redis 作者 Antirez 提出了 RedLock 算法,旨在通过多节点协作提升分布式锁的可靠性。本文将深入剖析 Redisson 中 RedLock 的实现原理、技术争议与最佳实践。
一、单节点 Redis 锁的范围性
1. 主从架构下的锁丢失问题
假设以下场景:
- 客户端 A 在 Redis 主节点成功获取锁。
- 主节点宕机,锁尚未同步到从节点。
- 从节点晋升为新主节点,此时客户端 B 也能获取同一把锁。
- 结果:客户端 A 和 B 同时持有锁,违反互斥性。
2. 异步复制的风险
Redis 主从复制默认异步,锁的写入大概在故障切换后丢失。
二、RedLock 算法核心思想
RedLock 的核心是通过 多个独立的 Redis 节点(至少 5 个)协作实现分布式锁。算法步骤如下:
1. 加锁流程
- 向所有节点发起加锁请求
客户端依次向 N 个独立 Redis 节点发送加锁下令:
- SET lock_key <unique_value> NX PX <expire_time>
复制代码
- 使用相同的 Key 和唯一值(如 UUID + 线程ID)。
- 设置公道的过期时间(通常为 10-30 秒)。
- 盘算有效锁数目
客户端统计成功得到锁的节点数。若 多数节点(≥ N/2 +1) 返回成功,则以为加锁成功。
示例:N=5 时,至少须要 3 个节点成功。
- 盘算锁的实际持有时间
锁的最终有效时间 = 初始过期时间 - 加锁过程耗时。
- 若实际持有时间过短(如剩余时间 < 业务实行时间),需立即释放锁。
2. 释放锁流程
向所有节点发送释放锁的 Lua 脚本(无论是否加锁成功):
- if redis.call("get",KEYS[1]) == ARGV[1] then
- return redis.call("del",KEYS[1])
- else
- return 0
- end
复制代码 三、Redisson 中 RedLock 的实现
1. 设置多节点 Redisson 客户端
需为每个独立 Redis 节点创建 RedissonClient 实例:
- Config config1 = new Config();
- config1.useSingleServer().setAddress("redis://node1:6379");
- RedissonClient client1 = Redisson.create(config1);
- Config config2 = new Config();
- config2.useSingleServer().setAddress("redis://node2:6379");
- RedissonClient client2 = Redisson.create(config2);
- // ... 创建其他节点客户端
- RLock lock1 = client1.getLock("myLock");
- RLock lock2 = client2.getLock("myLock");
- RLock lock3 = client3.getLock("myLock");
- 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() 实现非壅闭加锁:
- RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
- RFuture<Boolean> future = redLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
- future.whenComplete((res, ex) -> {
- if (res) {
- // 加锁成功
- }
- });
复制代码 六、对比其他方案
方案优点缺点适用场景Redis 单节点锁高性能、简单主从切换大概丢锁非关键业务、低并发RedLock高可靠性(多节点容错)性能较低、实现复杂高可靠性要求ZooKeeper 锁CP 模子、强一致性性能差、依赖 ZK 集群金融、政务系统etcd 锁高并发、强一致性(Raft 协议)功能较简单Kubernetes 生态、高并发 七、总结
Redisson 的 RedLock 实现通过多节点协作,明显提升了分布式锁的可靠性,但其复杂性、性能消耗和潜在风险(如时钟问题)需谨慎评估。技术选型发起:
- 通例场景:优先使用单节点 Redis 锁(结合业务幂等性)。
- 高可靠场景:评估 RedLock 或转向 ZooKeeper/etcd。
- 混合方案:对关键资源使用 RedLock,非关键资源使用单节点锁。
最终,分布式锁的可靠性不仅依赖中间件,还需结合业务层的容错设计(如事务补偿、异步校对),才能构建结实的分布式系统。
|