当前位置: 首页 > news >正文

黑马点评-Redisson-01_why_redisson

黑马点评 Redisson 一:为什么手写 Redis 分布式锁之后,还要学习 Redisson?

本文整理自我学习黑马点评 Redis 实战篇第 5 章「分布式锁 Redisson」的 5.1 和 5.2 小节。

第 4 章我们已经用 Redis 的setIfAbsent、过期时间、线程标识和 Lua 脚本手写过一个分布式锁。刚开始学到第 5 章时,我最大的疑惑是:既然自己已经能写出 Redis 分布式锁了,为什么还要引入 Redisson?它到底是另一个 Redis,还是 Java 里操作 Redis 的一个工具?

这篇文章就围绕这个问题展开:Redisson 为什么出现,它替代了我们手写锁的哪一部分,业务代码中RLocktryLock()unlock()到底在配合什么。


1. 为什么这一章不是简单换一个 API

学完前面的手写 Redis 分布式锁后,我们已经知道一把基本的 Redis 锁要处理这些事情:

1. 用一个 Redis key 表示锁。 2. 使用 setIfAbsent / SET NX 保证只有一个线程能创建锁。 3. 设置过期时间,防止服务宕机后锁永远不释放。 4. value 中保存线程标识,避免误删别人的锁。 5. 解锁时用 Lua 保证“判断锁归属 + 删除锁”的原子性。

到这里,一个很自然的问题就来了:

既然我们已经把 Redis 分布式锁写出来了,为什么讲义后面还要专门讲 Redisson?

一开始我也容易把 Redisson 理解成“又一种 Redis 用法”,好像它只是把SimpleRedisLock换成了RLock。但真正理解后会发现,Redisson 并不是在否定我们前面手写锁的价值。

更准确地说:

第 4 章手写 Redis 分布式锁,是为了理解底层原理;第 5 章学习 Redisson,是为了理解生产中更成熟的分布式锁封装。

手写锁帮我们看清分布式锁的基本骨架。Redisson 则是在这个骨架上继续补齐更多复杂能力,比如可重入、锁重试、自动续期、更多锁类型等。


2. Redisson 到底是什么

先把一个最容易误解的点说清楚:

Redisson 不是 Redis 服务器。

Redis 是服务端中间件,我们的 Java 项目通过网络连接 Redis。Redisson 是 Java 侧的 Redis 客户端框架,它帮我们更方便地使用 Redis 提供的各种能力。

你可以把关系理解成这样:

Java 业务代码 ↓ Redisson 客户端框架 ↓ Redis 服务端

在黑马点评第 5 章里,我们重点关注的是 Redisson 的分布式锁能力。它把很多锁相关的细节封装成了 Java 对象,比如:

RLocklock=redissonClient.getLock("lock:order:"+userId);booleanisLock=lock.tryLock();lock.unlock();

表面上看只是几个简单方法,底层其实还是在通过 Redis key、Lua 脚本、过期时间、线程标识等机制完成分布式协调。

所以 Redisson 的定位可以这样概括:

Redisson 是一个基于 Redis 的 Java 客户端框架,它把分布式锁这类复杂能力封装成了更易用的 Java API。


3. 手写锁已经解决了什么,又还缺什么

前面我们自己写的SimpleRedisLock已经能解决很多问题。

它大概具备这些能力:

1. 能通过 Redis key 抢锁。 2. 能设置锁过期时间,避免死锁。 3. 能保存线程标识,区分锁属于谁。 4. 能通过 Lua 脚本避免误删别人的锁。

这已经比最朴素的setnx + delete安全很多了。

但它仍然是一个教学版实现。真实项目里的锁会遇到更多复杂情况,例如:

1. 同一个线程重复获取同一把锁怎么办? 2. 抢锁失败后,是不是只能立刻失败?能不能等待一会儿再重试? 3. 锁过期时间怎么设置才合理?业务执行时间超过锁时间怎么办? 4. Redis 主从切换时,锁数据还没同步过去怎么办?

这些就是讲义 5.1 里列出的几个问题:

重入问题 不可重试 超时释放 主从一致性

其中 5.1、5.2 只是先告诉我们:这些问题存在,而 Redisson 提供了成熟封装。后面的 5.3、5.4、5.5 才逐步展开其中的原理。


4. Redisson 快速入门第一步:引入依赖

讲义中首先引入 Redisson 依赖:

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version></dependency>

这一步解决的问题是:

让项目能使用 Redisson 提供的 Java API。

没有这个依赖,项目里就没有RedissonClientRLockConfig这些类。

这里要注意,依赖只是把库引进来,还没有真正连接 Redis。连接 Redis 要靠下一步配置。


5. RedissonConfig:告诉 Redisson 去连接哪台 Redis

讲义中的配置类大概是这样:

@ConfigurationpublicclassRedissonConfig{@BeanpublicRedissonClientredissonClient(){Configconfig=newConfig();config.useSingleServer().setAddress("redis://<redis-host>:<redis-port>").setPassword("你的密码");returnRedisson.create(config);}}

这段代码里有几个新东西。

5.1@Configuration

它是什么:Spring 的配置类注解。

输入是什么:它标在类上,没有普通方法参数。

输出是什么:告诉 Spring 这个类里会定义一些 Bean。

为什么在这里使用:我们要把RedissonClient交给 Spring 管理,方便业务类中直接注入。

例子:

@ConfigurationpublicclassRedissonConfig{}

可以理解成:

这个类不是普通工具类,而是专门给 Spring 提供配置的。

5.2@Bean

它是什么:Spring 用来注册对象的方法级注解。

输入是什么:标在一个方法上。

输出是什么:方法返回值会被 Spring 容器管理。

为什么在这里使用:我们希望 Spring 容器里有一个RedissonClient,后面业务类可以通过@Resource注入。

例子:

@BeanpublicRedissonClientredissonClient(){returnRedisson.create(config);}

意思是:

Spring,帮我把这个方法返回的RedissonClient保存起来,后面别人要用时你负责注入。

5.3Config

它是什么:Redisson 的配置对象。

输入是什么:Redis 地址、密码、连接模式等。

输出是什么:配置本身不直接操作 Redis,而是传给Redisson.create(config)

为什么在这里使用:Redisson 不知道 Redis 在哪里,所以必须先配置连接信息。

例子:

Configconfig=newConfig();

5.4useSingleServer()

它是什么:告诉 Redisson 当前使用单 Redis 节点模式。

输入是什么:无。

输出是什么:返回一个单节点配置对象,可以继续链式调用。

为什么在这里使用:讲义当前小节是快速入门,只配置单 Redis 节点。

例子:

config.useSingleServer()

这句话可以翻译成:

Redisson,我现在只连接一台 Redis。

5.5setAddress()

它是什么:设置 Redis 地址。

输入是什么:Redis 连接地址,格式一般是redis://host:port

输出是什么:配置对象本身,方便继续链式调用。

为什么在这里使用:Redisson 需要知道 Redis 服务在哪里。

例子:

.setAddress("redis://<redis-host>:<redis-port>")

博客里不要写真实 IP、端口和密码,示例中统一用脱敏占位。

5.6Redisson.create(config)

它是什么:根据配置创建RedissonClient

输入是什么:前面配置好的Config

输出是什么:RedissonClient

为什么在这里使用:业务代码不能直接拿Config加锁,真正的操作入口是RedissonClient

例子:

returnRedisson.create(config);

一句话理解:

按照这份 Redis 配置,创建一个 Redisson 总入口对象。


6. 业务代码如何使用 Redisson 锁

讲义中的秒杀下单代码从手写锁切换成了 Redisson 锁。

核心代码是:

@ResourceprivateRedissonClientredissonClient;@OverridepublicResultseckillVoucher(LongvoucherId){// 查询秒杀券、判断时间、判断库存 ...LonguserId=UserHolder.getUser().getId();RLocklock=redissonClient.getLock("lock:order:"+userId);booleanisLock=lock.tryLock();if(!isLock){returnResult.fail("不允许重复下单");}try{IVoucherOrderServiceproxy=(IVoucherOrderService)AopContext.currentProxy();returnproxy.createVoucherOrder(voucherId);}finally{lock.unlock();}}

这段代码的业务目的和之前的SimpleRedisLock一样:

同一个用户的下单临界区,同一时刻只能有一个线程进入。

只是锁的实现从我们自己写,换成了 Redisson 的RLock


7.RedissonClientRLockgetLock()tryLock()分别是什么

7.1RedissonClient

它是什么:Redisson 的总入口对象。

输入是什么:业务使用时不需要自己创建,Spring 注入即可。

输出是什么:可以通过它获取各种 Redisson 对象,比如RLock

为什么在这里使用:业务代码需要通过它拿到一把分布式锁。

例子:

@ResourceprivateRedissonClientredissonClient;

可以把它理解成 Redisson 版的“操作入口”。

7.2getLock(String name)

它是什么:根据锁名称获取一个RLock对象。

输入是什么:锁名称,比如lock:order:10

输出是什么:RLock

为什么在这里使用:你必须先拿到锁对象,后面才能调用tryLock()unlock()

例子:

RLocklock=redissonClient.getLock("lock:order:"+userId);

注意:

getLock()不是加锁成功,它只是拿到锁对象。

真正加锁是下一步。

7.3RLock

它是什么:Redisson 提供的分布式锁对象。

输入是什么:由getLock()返回,业务代码一般不自己new

输出是什么:提供tryLock()unlock()等方法。

为什么在这里使用:替代我们自己写的SimpleRedisLock

例子:

RLocklock=redissonClient.getLock("lock:order:"+userId);

可以粗略理解成:

RLock是 Redisson 给业务代码提供的一把高级分布式锁。

7.4tryLock()

它是什么:尝试获取锁。

输入是什么:无参版本没有显式等待时间和锁释放时间。

输出是什么:boolean,成功返回true,失败返回false

为什么在这里使用:如果当前用户已经有请求拿到锁,重复请求就不能继续创建订单。

例子:

booleanisLock=lock.tryLock();

讲义中还演示了带参数版本:

booleanisLock=lock.tryLock(1,10,TimeUnit.SECONDS);

它的含义是:

最多等待 1 秒去获取锁。 获取锁成功后,锁 10 秒后自动释放。 时间单位是秒。

7.5unlock()

它是什么:释放锁。

输入是什么:无。

输出是什么:业务通常不关心返回值。

为什么在这里使用:拿到锁后,业务执行完必须释放。

例子:

finally{lock.unlock();}

为什么要放在finally

因为业务代码可能成功,也可能抛异常。只要锁拿到了,就应该尽量释放,避免后续请求一直拿不到锁。


8. Redisson 锁在秒杀下单中的执行流程

讲义当前小节的同步秒杀流程可以画成这样:

用户请求秒杀优惠券

查询秒杀券

是否未开始或已结束?

返回失败

库存是否小于 1?

返回库存不足

获取当前用户 userId

redissonClient.getLock(lock:order:userId)

lock.tryLock()

是否获取锁成功?

返回不允许重复下单

通过代理调用 createVoucherOrder

查订单 + 扣库存 + 创建订单

finally 中 unlock

这张图里最重要的是:

Redisson 锁保护的仍然是“一人一单”的临界区。

它不是替代库存乐观锁。库存是否超卖,仍然需要靠:

stock>0

这种条件更新来保证。


9. 如果不用 Redisson,会怎样

如果继续使用自己手写的SimpleRedisLock,不是不能跑,而是很多复杂能力需要自己继续补。

比如:

1. 可重入需要自己设计数据结构和计数。 2. 锁重试需要自己写等待和唤醒逻辑。 3. 锁自动续期需要自己设计后台续期机制。 4. Redis 多节点可靠性需要自己考虑更多边界。

这些问题不是初学阶段马上都要手写出来,但必须知道它们存在。

所以 Redisson 的价值不是“让代码少几行”这么简单,而是:

它把分布式锁从教学版实现,升级成了更成熟的工程封装。


10. 易错点

易错点一:把 Redisson 当成 Redis 服务器

Redisson 不是 Redis 服务端。它是 Java 客户端框架,底层仍然要连接 Redis。

易错点二:以为getLock()就是加锁

getLock()只是拿锁对象。真正尝试获取锁的是:

lock.tryLock();

易错点三:以为 Redisson 锁可以替代库存乐观锁

Redisson 这里锁的是用户维度:

lock:order:userId

它防的是同一用户重复下单。

库存是券维度的共享资源,多个不同用户仍然可能同时抢同一张券,所以还需要库存条件更新。

易错点四:忽略finally中释放锁

拿到锁后,不管业务成功还是失败,都应该释放锁。否则容易导致其他请求长期拿不到锁。

易错点五:混淆讲义代码和最终版项目代码

讲义 5.2 是同步下单中演示 Redisson 锁。最终版项目可能已经演进到异步下单流程。写博客时应以讲义当前小节为主,最终版项目只作为补充。


11. 面试回答

问:为什么已经手写了 Redis 分布式锁,还要使用 Redisson?

可以这样回答:

手写 Redis 分布式锁可以帮助我们理解底层原理,比如SET NX抢锁、设置过期时间防死锁、用线程标识防误删、用 Lua 保证解锁原子性。但生产中分布式锁还要考虑可重入、锁重试、自动续期、多种锁类型以及更复杂的 Redis 部署场景。Redisson 是成熟的 Redis Java 客户端框架,它把这些能力封装成RLock等对象,业务代码只需要获取锁、尝试加锁、释放锁即可。

问:RLock lock = redissonClient.getLock(...)这句是不是已经加锁?

可以这样回答:

不是。getLock()只是根据锁名称获取一个RLock锁对象,真正尝试获取锁的是tryLock()lock()方法。只有tryLock()返回true,才表示当前线程获取锁成功。

问:Redisson 锁解决了秒杀中的哪个问题?

可以这样回答:

在这段秒杀业务中,Redisson 锁主要解决一人一单的并发问题,也就是同一个用户的多个并发请求不能同时进入创建订单逻辑。它不直接替代库存乐观锁,因为库存是所有用户共享的券维度资源,仍然需要数据库条件更新来防止超卖。


12. 总结

这一篇主要讲清楚了 Redisson 的入门使用。

第 4 章手写SimpleRedisLock,重点是理解分布式锁的底层原理。第 5 章引入 Redisson,重点是理解成熟框架如何把这些复杂细节封装起来。

Redisson 不是 Redis 服务器,而是 Java 侧的 Redis 客户端框架。它通过RedissonClient提供入口,通过RLock提供分布式锁对象。业务代码通过getLock()获取锁对象,通过tryLock()尝试加锁,通过unlock()释放锁。

但要注意,Redisson 锁在秒杀下单中主要保护的是“一人一单”的用户维度临界区。库存超卖问题仍然要靠数据库层面的条件更新来保证。

下一篇继续看 Redisson 的第一个核心能力:可重入锁。也就是为什么同一个线程重复获取同一把锁时,不会把自己锁死。

http://www.gsyq.cn/news/1462333.html

相关文章:

  • CodeGraph 代码图谱实战:AI Agent 为什么不该再从 grep 开始?
  • 如何快速掌握LevelUI:LevelDB可视化管理的完整使用指南
  • 腾讯云代理行业深度拆解:避坑指南与合作选择
  • 3步掌握德州扑克最优策略:用TexasSolver免费从入门到精通的完整指南
  • Passage: The Apotheosis of the Twin Pincer
  • 小米MiMo邀请码最新(2026.06)
  • 浙江杨梅采摘体验指南:渚山杨梅园的硬核优势解析 - 奔跑123
  • GitHub中文翻译插件:3分钟实现GitHub界面全面本地化
  • 2026年 余杭区写字楼/未来科技城在租写字楼推荐榜单:优质办公空间与产业集聚价值深度解析 - 品牌企业推荐师(官方)
  • 售后完善:透明收费饮水机服务商在哪租 - 13425704091
  • DIY智能RGB壁灯:从电路连接到旧化涂装的完整制作指南
  • APK-Installer:Windows平台最便捷的安卓应用安装解决方案
  • STM32F103C6T6 UART转CAN通信工程:支持2Mbps高速透传,含CubeIDE工程与测试工具
  • 房价预测实战:用Sklearn的LinearRegression跑多元线性回归,结果不准?可能是最小二乘法的‘锅’
  • 2026年银川劳动纠纷律师避坑指南:5家靠谱专业推荐 - 本地品牌推荐
  • 10个必学的Linux命令及用法
  • DIY便携式电源:从18650电池组到300W逆变器的完整构建指南
  • 如何通过技术情报分析提升产业招商的针对性和成功率?
  • 基于树莓派与Arduino的激光钢琴:嵌入式系统与物联网实践
  • 高频链上事件监听:深入 Wagmi 异步交互机制与事件轮询底层
  • 理解Harness_Engineering_从提示词工程
  • 基于STM32F103与WS2812B的智能LED矩阵:从硬件设计到软件驱动的全栈实践
  • 【AI保险融合实战指南】:2024年7大落地场景、3类避坑红线与5家头部险企私有化部署路径
  • 基于PIC单片机与SPWM技术的正弦波逆变器设计实战
  • 红外光电计数器DIY:从传感器原理到电路实现的完整指南
  • 口碑好的店铺招牌,哪个才是你的心头好?
  • 为什么大批程序员扎堆转行网安?深度拆解背后4大核心原因
  • Snippy完整指南:快速单倍体变异检测与核心基因组比对工具终极教程
  • 防范智能合约数据溢出:编写以太坊安全审计规约的实战指南
  • 影刀RPA进阶:我写了一套调度引擎,500个店铺同时跑,内存稳得像条直线