聊到 Redis 就有一个话题始终绕不开------分布式锁。
用原生 Redis 手写分布式锁,需要自己处理锁超时、锁重入、锁释放等一系列边界问题,稍有不慎就是线上事故。Redisson 正是为解决这些问题而生的------它是 Redis 的 Java 客户端,但远不止"客户端"这么简单。
这篇文章从 Redisson 最核心的看门狗机制讲起,再展开它的完整能力矩阵。
主线:从"锁会过期"到"锁会自己续命"

第一站:看门狗机制------锁快过期了,谁来续命?
为什么需要看门狗?
分布式锁必须设置过期时间,否则持锁线程宕机后锁永远无法释放,造成死锁。
但过期时间设多少合适?
- 设短了 :业务还没执行完,锁就自动过期了,其他线程趁虚而入,数据不一致。
- 设长了 :持锁线程宕机后,其他线程要等很久才能拿到锁,系统可用性下降。
看门狗(Watch Dog) 就是来解决这个矛盾的------它让锁的过期时间随业务执行动态调整。
核心机制:30 秒 + 10 秒
看门狗的核心逻辑可以用三个数字概括:
| 数字 | 含义 | 说明 |
|---|---|---|
| 30 秒 | 锁的初始过期时间 | lockWatchdogTimeout 默认值 |
| 10 秒 | 续期检查间隔 | 30 秒的 1/3,即 internalLockLeaseTime / 3 |
| 30 秒 | 每次续期后的新过期时间 | 每次续期都把 TTL 重置回 30 秒 |

续期操作的底层实现
看门狗的续期不是简单发一个 EXPIRE 命令,而是通过一段 Lua 脚本在 Redis 端原子完成:
lua
-- 检查锁是否存在且被当前线程持有
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
-- 重新设置过期时间
redis.call('pexpire', KEYS[1], ARGV[1]);
return 1;
end;
return 0;
为什么必须用 Lua 脚本?
因为续期操作包含"检查 + 设置"两个步骤。如果分两次请求发送,中间可能发生:
- 客户端 A 检查锁存在 → 返回 OK
- 此时网络延迟,锁已过期被释放
- 客户端 B 抢到了锁
- 客户端 A 收到"锁存在"的响应,继续执行
EXPIRE→ 给 B 的锁续期了!
Lua 脚本在 Redis 中原子执行,杜绝了这种竞态问题。
续期的触发条件
看门狗并非在所有加锁场景下都会启动 。只有满足以下条件之一时才会触发:

这意味着 :如果你调用了 lock.lock(10, TimeUnit.SECONDS),看门狗就不会工作,锁在 10 秒后无条件释放 ------即使业务还没执行完。
核心要点 :使用
lock()无参方法才能享受看门狗的自动续期。如果你明确知道业务最长执行时间,可以手动指定leaseTime,此时需要自己确保业务能在超时前完成。
源码核心逻辑
java
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
Long ttl = null;
if (leaseTime != -1) {
// 指定了过期时间,直接加锁,不启动看门狗
ttl = tryLockInnerAsync(leaseTime, unit, threadId, ...);
} else {
// 未指定过期时间,使用看门狗超时时间(默认30秒)
ttl = tryLockInnerAsync(
commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
TimeUnit.MILLISECONDS, threadId, ...
);
// 启动看门狗
scheduleExpirationRenewal(threadId);
}
return ttl;
}
java
private void renewExpiration() {
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee == null) return;
// 通过Lua脚本延长锁的过期时间
RFuture<Boolean> future = renewExpirationAsync(threadId);
future.onComplete((success, e) -> {
if (success) {
// 续期成功,递归调用,实现循环续期
renewExpiration();
}
});
}
// 续期间隔为看门狗超时时间的1/3
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
看门狗什么时候停止?
| 停止场景 | 触发方式 |
|---|---|
| 正常释放锁 | 业务代码调用 unlock(),从全局 EXPIRATION_RENEWAL_MAP 中移除续期任务 |
| 线程异常中断 | 当前线程被中断或异常退出,续期任务自动清理 |
| 客户端实例关闭 | Redisson 客户端关闭时自动清理所有续期任务 |
| 锁被其他客户端强制删除 | 下次续期脚本检测失败,递归终止 |
看门狗的底层定时器
看门狗不是靠"轮询线程池"或"额外守护线程"实现的,而是使用 Netty 的 HashedWheelTimer 来实现轻量、精准的异步续期。这也是 Redisson 底层基于 Netty 通信架构的一个体现------它复用了 Netty 的时间轮来调度续期任务,不需要额外创建线程。
关键配置参数
java
Config config = new Config();
// 看门狗超时时间(默认 30000 毫秒)
config.setLockWatchdogTimeout(30000);
注意 :
lockWatchdogTimeout设置过短(如 100ms)可能导致锁频繁续期失败,建议保持默认值 30 秒或适当调大。
第二站:Redisson 锁家族------不只是可重入锁
Redisson 提供了一整套分布式锁实现,通过统一的 RLock 接口暴露。不同锁类型在可重入性、公平性、互斥范围 上各有取舍。
可重入锁(Reentrant Lock)
这是 Redisson 默认提供的分布式锁,也是使用最广泛的一种。核心特性:
- 可重入:同一个线程可以多次获取同一把锁,不会自己阻塞自己。
- 看门狗续期:默认启用自动续期。
- 阻塞等待:获取不到锁时,通过 Pub/Sub 等待锁释放通知,而非空轮询。
底层实现 :Redisson 的锁本质上是一个 Hash 结构 ------Key 是锁名,Field 是 UUID:ThreadId,Value 是重入次数。
text
lua
锁名: "myLock"
+--------------------------------------+
| UUID:ThreadId | 重入次数 |
| abc123:1 | 2 | ← 同一线程重入了2次
+--------------------------------------+
TTL: 30秒(看门狗每10秒续期一次)
java
@Autowired
private RedissonClient redissonClient;
public void doBusiness(Long goodsId) {
RLock lock = redissonClient.getLock("lock:" + goodsId);
try {
lock.lock(); // 默认启用看门狗
// 业务逻辑
} finally {
lock.unlock();
}
}
公平锁(Fair Lock)
普通锁在竞争时是非公平的------锁释放后,所有等待线程同时被唤醒,谁先抢到算谁的。
公平锁保证 FIFO(先进先出) ------先请求锁的线程一定先获得锁,不会出现"插队"现象。
实现原理 :公平锁在 Redis 中维护一个等待队列(基于 Redis List 或 ZSet),按请求时间排序。锁释放后,只唤醒队列头部的线程。
代价:公平锁的性能略低于非公平锁,因为需要维护队列和按顺序唤醒。大多数场景用默认的可重入锁就够了。
读写锁(ReadWriteLock)
允许多个线程同时读 ,但只允许一个线程写。适用于读多写少的场景。
实现原理 :读写锁在 Redis 中用一个 Hash 结构 存储,加锁时通过 mode 字段区分读锁还是写锁:
text
锁名: "anyRWLock"
+--------------------------------------------------+
| mode | read |
| UUID:ThreadId | 1(读锁重入次数) |
| {anyRWLock}:UUID:ThreadId:rwlock_timeout:1 | 1 |
+--------------------------------------------------+
- 读锁:多个线程可以同时持有,每加一次锁,重入计数 +1。
- 写锁:与所有读锁、其他写锁互斥。
- 锁升级/降级:Redisson 读写锁支持锁降级(写锁降为读锁),但不支持锁升级(读锁升为写锁),因为升级容易导致死锁。
java
RReadWriteLock rwLock = redissonClient.getReadWriteLock("myRWLock");
RLock readLock = rwLock.readLock();
RLock writeLock = rwLock.writeLock();
// 读操作
readLock.lock();
try { /* 读取数据 */ } finally { readLock.unlock(); }
// 写操作
writeLock.lock();
try { /* 修改数据 */ } finally { writeLock.unlock(); }
联锁(MultiLock)
当需要同时锁定多个资源 时使用。联锁会把多个 RLock 组合成一个逻辑锁,只有所有锁都获取成功才算加锁成功。任意一个获取失败,已获取的锁会被释放。
java
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RLock multiLock = redissonClient.getMultiLock(lock1, lock2, lock3);
multiLock.lock();
try { /* 业务逻辑 */ } finally { multiLock.unlock(); }
红锁(RedLock)------ 不推荐使用
RedLock 是 Redis 作者 Antirez 提出的算法,需要在多个独立的 Redis 节点上同时加锁,超过半数成功才算加锁成功。
为什么不推荐?
- 性能差:每次加锁需要访问 N 个节点,网络开销大。
- 运维复杂:需要维护多个独立 Redis 实例,不能使用主从或集群。
- 争议大:分布式系统专家 Martin Kleppmann 曾与 Antirez 就 RedLock 的安全性展开过激烈辩论,核心争议在于时钟漂移场景下 RedLock 无法保证安全性。
- 有替代方案:对于需要强一致性的场景,建议使用 ZooKeeper 或 etcd 的分布式锁。
建议 :除非有非常特殊的强一致性需求,否则不要使用 RedLock。Redisson 从 3.17.0 版本开始已经弃用 RedLock,官方推荐使用普通的 RLock 配合哨兵/集群模式。
锁类型对比总结
| 锁类型 | 可重入 | 公平/FIFO | 互斥范围 | 典型场景 |
|---|---|---|---|---|
| 可重入锁 | ✅ | ❌ | 单资源 | 通用互斥 |
| 公平锁 | ✅ | ✅ | 单资源 | 需要严格 FIFO 顺序 |
| 读写锁 | ✅ | ❌ | 读共享/写互斥 | 读多写少 |
| 联锁 | ✅ | ❌ | 多资源同时锁定 | 跨资源原子操作 |
| 红锁 | ✅ | ❌ | 多节点 | 不推荐 |
第三站:分布式限流器 RRateLimiter
除了分布式锁,Redisson 还提供了开箱即用的分布式限流器。其底层基于令牌桶算法 实现。
核心原理
- 系统以固定速率向桶中添加令牌。
- 请求需要获取令牌才能被处理,没有令牌则被拒绝。
- 通过调整令牌生成速率和桶容量,可精确控制请求速率。
- 令牌桶的状态存储在 Redis 中,多节点共享,确保分布式环境下的一致性。
使用示例
java
// 获取限流器(每秒生成5个令牌,桶容量为5)
RRateLimiter rateLimiter = redissonClient.getRateLimiter("api:limiter");
rateLimiter.trySetRate(RateType.OVERALL, 5, 1, RateIntervalUnit.SECONDS);
// 非阻塞获取令牌
boolean acquired = rateLimiter.tryAcquire(1);
if (acquired) {
// 处理请求
} else {
// 拒绝请求(限流)
}
// 阻塞式获取令牌(最多等待5秒)
boolean res = rateLimiter.tryAcquire(1, 5, TimeUnit.SECONDS);
两种限流模式
| 模式 | 说明 | 适用场景 |
|---|---|---|
| OVERALL | 全局限流,所有节点共享同一个令牌桶 | 全局限流(如全局限流 1000 QPS) |
| PER_CLIENT | 客户端级限流,每个节点独立计数 | 单节点限流(如单节点限流 200 QPS) |
第四站:延迟队列 RDelayedQueue
Redisson 的延迟队列用于实现"延迟消费"场景,比如订单 30 分钟未支付自动取消。
核心原理
RDelayedQueue 的设计很巧妙------它并非一个单一的 Redis 数据结构,而是结合了 ZSET(有序集合) 和 LIST(列表,即 RBlockingQueue) 来实现的。

三个队列的分工:
| 队列 | 结构 | 作用 |
|---|---|---|
| 消息延时队列 | ZSET(score=到期时间戳) | 按到期时间排序,快速找到下一个到期的消息 |
| 消息顺序队列 | LIST | 按消息添加顺序存储,移除消息时按顺序删除 |
| 消息目标队列 | LIST | 存放到期的消息,供消费端阻塞获取 |
使用示例
java
// 生产者端
RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("delay-queue");
RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
// 发送延迟5秒的消息
delayedQueue.offer("测试延迟消息", 5, TimeUnit.SECONDS);
// 消费者端
String msg = blockingQueue.take(); // 阻塞等待到期消息
// 处理业务逻辑
第五站:布隆过滤器 RBloomFilter
Redisson 封装了分布式布隆过滤器,用于高效判断元素是否存在。
核心原理
布隆过滤器是一种概率型数据结构,基于哈希函数和位数组实现:
-
添加元素:将元素通过多个哈希函数映射到位数组的多个位置,置为 1。
-
判断存在:
- 若所有对应位全为 1 → 可能存在(有误判概率)。
- 若存在任意一位为 0 → 一定不存在。
Redisson 将位数组存储在 Redis 中,支持跨节点共享,误判率默认为 0.03(3%)。
使用示例
java
// 初始化布隆过滤器(预计100万个元素,误判率1%)
RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("userBloomFilter");
bloomFilter.tryInit(1000000, 0.01);
// 添加元素
bloomFilter.add("user123");
// 判断元素是否存在
boolean mightExist = bloomFilter.contains("user123"); // 可能存在
boolean notExist = bloomFilter.contains("unknown"); // 一定不存在
第六站:最佳实践------避坑指南
看门狗的坑
| 坑 | 说明 | 解决方案 |
|---|---|---|
| 指定了 leaseTime,看门狗不续期 | 调用 lock.lock(10, SECONDS) 时看门狗不启动 |
使用 lock() 无参方法 |
| 看门狗续期失败 | Redis 连接断开导致续期失败 | 确保 Redis 高可用(哨兵/集群),监控续期失败日志 |
| 业务执行时间过长 | 即使续期,业务线程也可能被中断 | 设置合理业务超时,避免无限循环 |
锁使用的最佳实践

核心避坑要点:
- 必须在
finally块中释放锁------否则业务异常会导致锁无法释放,即使有看门狗也会造成死锁。 - 获取锁后,再发起加锁判断 ------Redisson 的
unlock()会检查当前线程是否持有锁,未持有会抛异常。 - Redisson 锁是"咨询锁" ------它只协调通过相同锁对象加锁的客户端,不阻止直接操作 Redis 的客户端。
- 主从切换场景下的锁安全 :Redisson 默认启用了从节点同步检查 ------加锁时会等待锁写入至少一个从节点才返回成功,降低主从切换导致锁丢失的风险。
终章:Redisson 能力全景

| 问题 | Redisson 方案 | 一句话 |
|---|---|---|
| 分布式锁怕超时 | 看门狗机制 | 30 秒超时,10 秒续期,业务没结束锁就一直续 |
| 多线程同时读 | 读写锁 | 读共享、写互斥,读多写少场景利器 |
| 需要 FIFO 顺序 | 公平锁 | 先到先得,不插队 |
| 同时锁多个资源 | 联锁 | 要么全锁,要么全不锁 |
| 限制请求速率 | RRateLimiter | 令牌桶算法,多节点共享限流 |
| 延迟消费 | RDelayedQueue | ZSET + LIST,到期自动转移 |
| 防缓存穿透 | RBloomFilter | 跨节点共享,高效去重判断 |
Redisson 的核心价值在于:它把分布式场景下的各种并发问题,都封装成了开箱即用的组件。 看门狗解决了锁续期的痛点,读写锁和公平锁提供了更细粒度的并发控制,延迟队列和布隆过滤器则覆盖了更多业务场景。理解了这些组件的底层原理,才能在实战中选对工具、避开陷阱。