Redisson 分布式锁:MultiLock、RedLock 与 RFencedLock
一、三种锁的区别
三者的核心区别:
MultiLock:多把锁必须全部获取成功。
RedLock:历史上通过多数派机制实现多节点加锁。
RFencedLock:通过递增 Token,让受保护资源拒绝旧线程的操作。
二、MultiLock 联锁
1. 基本原理
MultiLock 可以将多把独立的 RLock 组合在一起。
只有全部成员锁获取成功,才算联锁获取成功。
例如,一个业务需要同时锁定:
lock:order:1001
lock:stock:2001
lock:coupon:3001执行过程:
申请 MultiLock
│
├── 订单锁:成功
├── 库存锁:成功
└── 优惠券锁:成功
│
▼
联锁获取成功
│
▼
执行业务如果有一把锁获取失败,Redisson 会在等待预算内进行协调和重试;最终获取失败时,会释放本次已经获取的成员锁。
2. Java 8 使用
RLock orderLock = redissonClient.getLock("lock:order:1001");
RLock stockLock = redissonClient.getLock("lock:stock:2001");
RLock couponLock = redissonClient.getLock("lock:coupon:3001");
RLock multiLock = redissonClient.getMultiLock(
orderLock,
stockLock,
couponLock
);
boolean acquired = false;
try {
acquired = multiLock.tryLock(3, TimeUnit.SECONDS);
if (!acquired) {
throw new RuntimeException("获取联锁失败");
}
// 执行业务
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁被中断", e);
} finally {
if (acquired && multiLock.isHeldByCurrentThread()) {
multiLock.unlock();
}
}3. 注意事项
MultiLock 可以组合相同或不同 Redis 实例中的锁。
MultiLock 不保证所有成员锁在同一瞬间原子获取。
MultiLock 不能替代数据库事务。
获取多把锁会增加 Redis 操作和协调成本。
如果只是更新 MySQL 中的多张表,通常优先使用数据库事务,不一定需要 MultiLock。
三、RedLock 红锁
1. 为什么需要 RedLock?
普通 Redis 分布式锁存在主从切换风险。
例如:
线程 A 获取锁成功
↓
Redis Master 宕机
↓
锁数据尚未复制到 Replica
↓
Replica 提升为新 Master
↓
线程 B 再次获取同一把锁
↓
两个线程都可能执行业务由于 Redis 主从复制通常是异步的,因此存在锁数据丢失风险。
Redlock 算法尝试通过多个独立 Redis Master 降低单节点故障造成的影响。
2. 多数派原理
假设存在5个独立 Redis Master:
Redis 1:成功
Redis 2:成功
Redis 3:成功
Redis 4:失败
Redis 5:失败满足多数派条件:
成功节点数 > 总节点数 / 2即:
5个节点,至少需要3个成功但还需要满足获取锁总耗时小于租约有效期等条件,并在获取失败时尽可能释放已获取的锁。
注意:这里必须是多个独立 Redis Master,不能把普通主从架构中的从节点当成独立投票节点。
3. 目前是否推荐?
当前 Redisson 官方已经将 RedLock API 标记为 Deprecated(弃用)。
原因在于多节点锁仍然存在时钟、暂停、网络故障等可靠性边界,不能保证绝对互斥。
新项目一般不推荐直接使用 RedLock。
普通业务优先使用 RLock;对过期持锁者的写入风险有要求时,可以考虑 RFencedLock 并配合资源端校验。
四、RFencedLock 栅栏锁
1. 解决什么问题?
RFencedLock 主要解决:
分布式锁已经过期,但原持锁线程仍然继续执行业务,可能覆盖其他线程的数据。
例如普通 RLock:
1. 线程 A 获取锁
↓
2. JVM 长时间暂停,无法续期
↓
3. 锁过期
↓
4. 线程 B 获取锁,更新数据库
↓
5. 线程 A 恢复,继续更新数据库
↓
6. 可能覆盖线程 B 的数据问题在于:
Redis 锁过期,不会自动终止正在执行的 Java 线程。
Watch Dog 可以降低锁提前过期的风险,但无法彻底避免 JVM 长时间暂停、网络中断等问题。
2. RFencedLock 的解决原理
RFencedLock 在成功获取锁时,可以返回一个递增的 Fencing Token(栅栏令牌)。
例如:
线程 A 获取锁 → Token = 100
↓
线程 A 长时间暂停
↓
锁过期
↓
线程 B 获取锁 → Token = 101
↓
线程 B 更新数据
数据库记录 Token = 101
↓
线程 A 恢复
携带 Token = 100 更新数据
↓
数据库发现 100 < 101
↓
拒绝线程 A 的旧操作核心原理:通过单调递增的 Token 区分新旧持锁者,由受保护资源拒绝过期 Token。
需要特别注意:
RFencedLock 不会自动停止旧线程。
Token 必须传递给数据库或外部服务。
受保护资源必须原子校验并记录 Token。
只有令牌被资源端正确校验,才能真正防止旧请求覆盖新请求。
另外,Token 101 的生成本身不会自动阻止携带 Token 100 的操作。如果旧操作在新 Token 被资源端记录之前执行,仍可能被接受,因此令牌机制不能替代数据库事务和完整的业务一致性设计。
3. Java 8 使用示例
RFencedLock lock =
redissonClient.getFencedLock("lock:stock:1001");
Long token = lock.lockAndGetToken();
try {
// 将 Token 传给业务资源进行校验
stockService.updateStockWithToken(1001L, token);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}注意:updateStockWithToken() 需要由业务自行实现资源端的原子校验。仅调用 lockAndGetToken() 并不能自动保护数据库。
使用前还需要确认项目中的 Redisson 版本支持该 API。
五、RFencedLock 与 Watch Dog 的区别
两者不是相互替代的关系。
Watch Dog:尽可能保证正常执行的线程持续持有锁。
RFencedLock:即使锁已经失效,也能通过资源端令牌校验拒绝落后的写入。
六、实际开发如何选择?
对于库存扣减等简单操作,可以直接使用:
UPDATE product
SET stock = stock - 1
WHERE id = #{productId}
AND stock > 0;通过判断受影响行数确保库存不会扣成负数。这种场景通常不需要引入复杂的分布式锁机制。
七、面试总结
1. MultiLock 是什么?
MultiLock 是 Redisson 提供的联锁机制,可以将多把独立的分布式锁组合在一起,要求全部成员锁获取成功,才算联锁获取成功。
2. MultiLock 和 RedLock 有什么区别?
MultiLock 要求全部成员锁获取成功,而历史上的 RedLock 算法通过多个独立 Redis Master 的多数派方式尝试实现分布式锁。当前 Redisson 已弃用 RedLock API。
3. RFencedLock 解决什么问题?
RFencedLock 主要解决分布式锁过期后,旧线程恢复执行导致的数据一致性问题。
它通过递增的 Fencing Token 标识持锁者的新旧顺序,由数据库或其他资源端原子校验 Token,拒绝落后的写入。
4. RFencedLock 和普通 RLock 的区别?
普通 RLock 主要保证正常情况下的互斥执行,而 RFencedLock 额外提供递增 Token,用于识别和拒绝过期持锁者的操作。
5. Watch Dog 和 RFencedLock 有什么区别?
Watch Dog 负责自动续期,降低锁提前失效的风险;RFencedLock 通过令牌校验防止旧持锁者覆盖新数据。
八、核心记忆
MultiLock:多把锁,全部获取成功才算成功。
RedLock:多独立 Redis Master 的多数派加锁算法,当前官方已弃用对应 API。
RFencedLock:递增 Token + 资源端校验,防止过期持锁者的旧操作覆盖新数据。
Watch Dog:自动续期,尽量避免锁提前过期。
一句话总结:MultiLock 解决多把锁的组合协调问题,RedLock 尝试降低单节点故障风险,RFencedLock 解决旧持锁者在锁失效后继续写入的问题。
