0
0
0

Redisson 分布式锁:MultiLock、RedLock 与 RFencedLock

2026-10-26
文章摘要
|

一、三种锁的区别

锁类型

核心作用

解决的问题

MultiLock(联锁)

将多把锁组合成一把锁

需要同时持有多把锁

RedLock(红锁)

基于多个独立 Redis 节点的多数派加锁算法

尝试降低单节点故障风险

RFencedLock(栅栏锁)

获取锁时生成递增 Token

防止过期持锁者继续修改数据

三者的核心区别:

  • 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. 注意事项

  1. MultiLock 可以组合相同或不同 Redis 实例中的锁。

  2. MultiLock 不保证所有成员锁在同一瞬间原子获取。

  3. MultiLock 不能替代数据库事务。

  4. 获取多把锁会增加 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

主要目的

防止锁正常执行期间提前过期

防止旧持锁者错误写入

实现方式

定时续期

递增 Token + 资源端校验

能否延长 TTL

可以

锁本身也可使用看门狗

能否阻止旧线程继续执行

不能

不能直接终止线程

能否拒绝过期持锁者写入

不负责

资源端正确校验时可以

两者不是相互替代的关系。

  • Watch Dog:尽可能保证正常执行的线程持续持有锁。

  • RFencedLock:即使锁已经失效,也能通过资源端令牌校验拒绝落后的写入。

六、实际开发如何选择?

业务场景

推荐方案

普通订单处理、防重复执行

RLock

同时需要持有多把独立锁

MultiLock

Redis 多节点多数派加锁

了解 RedLock 原理,不推荐新项目使用弃用 API

旧持锁者可能覆盖新数据

RFencedLock + 资源端 Token 校验

单行库存扣减

优先 MySQL 原子条件更新

多表数据一致性

数据库事务,必要时配合分布式锁

对于库存扣减等简单操作,可以直接使用:

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 解决旧持锁者在锁失效后继续写入的问题。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论