这是我学习 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 还是解决不了三件事:
- 不可重入:同一个线程方法里嵌套加锁,第二次
setIfAbsent会失败——自己把自己卡死。 - 不续期:锁 30 秒过期,业务要跑 40 秒,锁中途没了,别的请求挤进来。
- 主从切换丢锁:主节点挂了,锁数据没同步到从节点,另一个客户端在从节点又抢到锁。
Redisson 的 RLock 就是为解决这三件事设计的。
三、分布式锁实战(RLock)
RLock lock = redissonClient.getLock("lock:stock:1001");
lock.lock(); // 默认 30 秒 + 看门狗自动续期
try {
// 业务逻辑:扣库存、发优惠券……
} finally {
lock.unlock(); // 内部校验持有者,非持锁线程会抛异常
}两个常用变体:
// 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 一定不存在",把非法请求挡在缓存前面:
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)—— 接口限流
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 里的分布式集合,跨机器共享数据:
RMap<String, String> map = redissonClient.getMap("config:map");
map.put("key", "value");
String v = map.get("key");七、客户端怎么选(对照表)
| 客户端 | 特点 | 适用 |
|---|---|---|
| Jedis | 同步阻塞、API 简单、线程不安全需连接池 | 老项目、简单场景 |
| Lettuce | Netty 非阻塞、线程安全 | Spring Data Redis 默认,通用首选 |
| Redisson | 高级分布式对象封装 | 需要分布式锁/限流/布隆/分布式集合 |
结论:连接和基础读写用 Lettuce(Spring 默认),需要分布式锁、限流、布隆这些"高级货"时叠加 Redisson,各干各的。
八、Redisson 的坑
- unlock 必须放 finally:业务抛异常没走到 unlock,锁要等看门狗续期失败(或锁过期)才释放,导致其他请求长时间拿不到锁。
- 谁加锁谁释放:
unlock()会校验当前线程是否是持有者,非持锁线程调用会抛IllegalMonitorStateException。 - 指定了 leaseTime 就没有看门狗:
lock(10, SECONDS)是固定 10 秒,超时自动释放,业务没跑完就自己承担"锁提前释放"的后果。 - RedLock 有争议:官方提供的 RedLock(多节点过半加锁)依赖时钟同步、开销大,业界对其有效性有争论。多数场景主从 + 等待从节点同步就够了,别一上来就上 RedLock。
九、生产实践:lock4j 封装
手写 RLock 虽然能跑通,但生产项目里更常用 lock4j 这类框架封装——它在 Redisson 之上包了一层注解,开箱即用,还支持 SpEL 动态 key、自动续期(继承 Redisson 的看门狗)。
lock4j 是 baomidou(MyBatis-Plus 团队)出品的分布式锁框架,底层基于 Redisson。
依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<version>2.2.7</version>
</dependency>用法一:注解方式(最常用,AOP 自动加锁、方法结束自动释放):
@Lock4j(keys = {"#key"}) // SpEL 表达式动态指定锁 key
@GetMapping("/test")
public R<String> test(String key, String value) {
// 业务逻辑,方法结束自动释放锁
return R.ok(value);
}用法二:模板方式(手动控制,需自己 finally 释放):
@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 等。
两个注意点:
- SpEL key 要写对:
keys = {"#key"}里写错参数名,多个请求会共用一个 key,锁粒度被放大,性能反而下降。 - 模板方式要判空:
lock()返回null表示"没抢到锁",不判空直接finally释放会空指针。
小结
- Redisson = 基于 Redis 的分布式工具箱,不是普通命令客户端(Jedis/Lettuce 才是)
- 分布式锁
RLock:可重入(hash + 计数)+ 看门狗自动续期(默认 30s、每 10s 续)+ 校验持有者释放 - 看门狗只在不指定 leaseTime 时生效;续期和释放都用 Lua 保证原子
- 其他常用:布隆过滤器(防穿透)、RRateLimiter(限流)、分布式集合
- 生产实践:分布式锁常用 lock4j 封装(
@Lock4j注解),不用手写 RLock - 选型:Lettuce 做基础读写,Redisson 做分布式锁/限流/布隆,叠加使用
想回顾"为什么会有 Redisson、裸用 Redis 做锁的演进过程",看《Redis 分布式锁详解》;想了解 Redis 本身的数据结构和用法,看《Redis 快速入门》。
