Skip to content

这是我学习 Redisson 时整理的笔记。在《Redis 分布式锁详解》里,我讲到"裸用 Redis 做分布式锁,到 v6 就得上 Redisson 补可重入、看门狗、高可用",但没展开。这篇把 Redisson 讲清楚:它和 Jedis/Lettuce 到底什么区别、分布式锁和看门狗是怎么实现的,以及它还能帮我们做什么。


一、Redisson 是什么

先说清楚它和另外两个常听到的名字的关系。

  • Jedis:老牌 Redis Java 客户端,同步阻塞,API 简单直接,但线程不安全(要用连接池)。
  • Lettuce:基于 Netty 的非阻塞客户端,线程安全,是 Spring Data Redis 的默认客户端。
  • Redisson:也是 Redis 客户端(基于 Netty),但它的重点不是"发命令",而是在 Redis 之上封装了一堆分布式对象——分布式锁、分布式集合、布隆过滤器、限流器、发布订阅,开箱即用。

一句话:Jedis/Lettuce 是"命令发送器",Redisson 是"分布式工具箱"。 你要自己拼 SET NX EX + Lua 才能做锁,Redisson 直接给你一个 RLock,内部帮你处理了续期、可重入、释放这些细节。

所以它们不是二选一:Spring 项目里 Lettuce 负责连接和基本操作,需要分布式锁/限流/布隆时再引入 Redisson,两者共存。

二、为什么需要 Redisson(承接分布式锁的遗留问题)

《Redis 分布式锁详解》里裸用 SET NX EX + Lua 释放,到 v5 还是解决不了三件事:

  1. 不可重入:同一个线程方法里嵌套加锁,第二次 setIfAbsent 会失败——自己把自己卡死。
  2. 不续期:锁 30 秒过期,业务要跑 40 秒,锁中途没了,别的请求挤进来。
  3. 主从切换丢锁:主节点挂了,锁数据没同步到从节点,另一个客户端在从节点又抢到锁。

Redisson 的 RLock 就是为解决这三件事设计的。

三、分布式锁实战(RLock)

java
RLock lock = redissonClient.getLock("lock:stock:1001");

lock.lock();                    // 默认 30 秒 + 看门狗自动续期
try {
    // 业务逻辑:扣库存、发优惠券……
} finally {
    lock.unlock();              // 内部校验持有者,非持锁线程会抛异常
}

两个常用变体:

java
// 1. 尝试加锁:最多等 3 秒,拿不到就放弃(不阻塞死等)
boolean got = lock.tryLock(3, TimeUnit.SECONDS);
if (got) {
    try { /* 业务 */ } finally { lock.unlock(); }
}

// 2. 指定锁时长:10 秒后自动释放(注意:手动指定 leaseTime 就不走看门狗了)
lock.lock(10, TimeUnit.SECONDS);

四、看门狗(watchdog)是怎么续期的

lock() 不传超时时间时,Redisson 默认锁 30 秒,并启动一个看门狗定时任务:每 10 秒检查一次,只要业务线程还活着,就把锁续期回 30 秒。

关键细节:

  • 续期间隔 = 锁时长 / 3(30s → 每 10s 续一次),留足余量防止续期失败导致锁过期。
  • 只有不指定 leaseTime 时才启用看门狗。一旦你写 lock(10, TimeUnit.SECONDS),就是固定 10 秒过期,Redisson 不再续期——适合"业务时长可预估"的场景。
  • 续期和释放都是通过 Lua 脚本保证原子性的,不是 Java 侧拼几条命令。

这就是它和裸用 Redis 最大的区别:你不需要自己估业务要跑多久,锁会跟着业务一直续下去,业务结束 unlock 立即释放

五、可重入是怎么实现的

Redisson 的可重入锁,底层用的是 Redis 的 Hash 结构

key     = "lock:stock:1001"        (锁名)
field   = 持有者线程的唯一标识      (哪个线程持有)
value   = 重入次数                  (这个线程加了几次锁)
  • 每次 lock()value + 1(同一线程重复加锁,次数累加)
  • 每次 unlock()value - 1
  • 减到 0:才真正删除这个 key(释放锁)

所以同一个线程嵌套调用 lock() 两次不会死锁,unlock() 两次才真正释放——这就是"可重入"。

六、Redisson 还能做什么(三个最常用的)

1. 布隆过滤器(RBloomFilter)—— 防缓存穿透

缓存穿透是"查一个根本不存在的 key",每次都打到数据库。布隆过滤器能快速判断"这个 key 一定不存在",把非法请求挡在缓存前面:

java
RBloomFilter<String> filter = redissonClient.getBloomFilter("user:bloom");
filter.tryInit(100_000L, 0.03);   // 预计元素数 + 误判率

filter.add("user:1001");
boolean exists = filter.contains("user:1001");   // true
boolean maybe = filter.contains("user:9999");    // false:一定不存在,直接拦截

布隆过滤器的原理(位图 + 哈希、为什么能判断"不存在"、误判率、代价)见我整理的《布隆过滤器是什么》。

2. 限流器(RRateLimiter)—— 接口限流

java
RRateLimiter limiter = redissonClient.getRateLimiter("limit:api");
limiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.SECONDS); // 每秒 100 个

if (limiter.tryAcquire()) {
    // 放行
} else {
    // 限流,直接拒绝
}

3. 分布式集合(RMap / RList / RQueue / RSet)

像用本地集合一样用 Redis 里的分布式集合,跨机器共享数据:

java
RMap<String, String> map = redissonClient.getMap("config:map");
map.put("key", "value");
String v = map.get("key");

七、客户端怎么选(对照表)

客户端特点适用
Jedis同步阻塞、API 简单、线程不安全需连接池老项目、简单场景
LettuceNetty 非阻塞、线程安全Spring Data Redis 默认,通用首选
Redisson高级分布式对象封装需要分布式锁/限流/布隆/分布式集合

结论:连接和基础读写用 Lettuce(Spring 默认),需要分布式锁、限流、布隆这些"高级货"时叠加 Redisson,各干各的。

八、Redisson 的坑

  1. unlock 必须放 finally:业务抛异常没走到 unlock,锁要等看门狗续期失败(或锁过期)才释放,导致其他请求长时间拿不到锁。
  2. 谁加锁谁释放unlock() 会校验当前线程是否是持有者,非持锁线程调用会抛 IllegalMonitorStateException
  3. 指定了 leaseTime 就没有看门狗lock(10, SECONDS) 是固定 10 秒,超时自动释放,业务没跑完就自己承担"锁提前释放"的后果。
  4. RedLock 有争议:官方提供的 RedLock(多节点过半加锁)依赖时钟同步、开销大,业界对其有效性有争论。多数场景主从 + 等待从节点同步就够了,别一上来就上 RedLock。

九、生产实践:lock4j 封装

手写 RLock 虽然能跑通,但生产项目里更常用 lock4j 这类框架封装——它在 Redisson 之上包了一层注解,开箱即用,还支持 SpEL 动态 key、自动续期(继承 Redisson 的看门狗)。

lock4j 是 baomidou(MyBatis-Plus 团队)出品的分布式锁框架,底层基于 Redisson。

依赖:

xml
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
    <version>2.2.7</version>
</dependency>

用法一:注解方式(最常用,AOP 自动加锁、方法结束自动释放):

java
@Lock4j(keys = {"#key"})          // SpEL 表达式动态指定锁 key
@GetMapping("/test")
public R<String> test(String key, String value) {
    // 业务逻辑,方法结束自动释放锁
    return R.ok(value);
}

用法二:模板方式(手动控制,需自己 finally 释放):

java
@Autowired
private LockTemplate lockTemplate;

// lock(key, 过期时间, 获取锁超时时间, 执行器)
LockInfo lockInfo = lockTemplate.lock(key, 30000L, 5000L, RedissonLockExecutor.class);
if (lockInfo == null) {
    throw new RuntimeException("业务处理中,请稍后再试");   // 没抢到锁
}
try {
    // 业务逻辑
} finally {
    lockTemplate.releaseLock(lockInfo);                   // 释放锁
}

lock4j 帮你省掉的事:

  • 不用手写 unlock:注解方式 AOP 自动加锁/释放,不会漏 finally
  • SpEL 动态 key#id 直接取方法参数当锁 key,锁粒度灵活。
  • 自动续期:继承 Redisson 看门狗,业务多久锁就跟多久。
  • 多执行器:默认 Redisson,也支持 RedisTemplate、ZooKeeper 等。

两个注意点:

  1. SpEL key 要写对keys = {"#key"} 里写错参数名,多个请求会共用一个 key,锁粒度被放大,性能反而下降。
  2. 模板方式要判空lock() 返回 null 表示"没抢到锁",不判空直接 finally 释放会空指针。

小结

  • Redisson = 基于 Redis 的分布式工具箱,不是普通命令客户端(Jedis/Lettuce 才是)
  • 分布式锁 RLock:可重入(hash + 计数)+ 看门狗自动续期(默认 30s、每 10s 续)+ 校验持有者释放
  • 看门狗只在不指定 leaseTime 时生效;续期和释放都用 Lua 保证原子
  • 其他常用:布隆过滤器(防穿透)、RRateLimiter(限流)、分布式集合
  • 生产实践:分布式锁常用 lock4j 封装(@Lock4j 注解),不用手写 RLock
  • 选型:Lettuce 做基础读写,Redisson 做分布式锁/限流/布隆,叠加使用

想回顾"为什么会有 Redisson、裸用 Redis 做锁的演进过程",看《Redis 分布式锁详解》;想了解 Redis 本身的数据结构和用法,看《Redis 快速入门》。