
如果你在面试或实际开发中被问到“分布式锁”Redisson 基本是绕不开的名字。这几年我面试别人的时候几乎每次都会问“分布式锁你怎么实现”答案从SETNX手写、到 ZooKeeper 临时节点、再到 Redisson 都有。但聊到最后大家基本都会承认生产环境用 Redisson 最省心因为它把分布式锁的细节都封装好了。这篇内容我会从 Redisson 的设计思路、使用方式、底层原理再到我踩过的坑和排查思路完整地过一遍希望能给你的项目落地提供参考。1. 分布式锁到底是什么场景1.1 单机锁为什么不够用很多业务系统早期都是单机部署线程之间争抢资源时加个synchronized或者ReentrantLock就够了。但一旦服务变成多实例部署同一个请求可能被负载均衡到不同的机器上这时候单机的锁就完全失效了。举个典型的例子库存扣减。两台服务器同时处理订单都读到库存剩 1 件各自扣减成功结果超卖了。这类问题在单体架构下加锁就能解决但分布式环境下锁必须从“进程内”提升到“跨进程”的层面让所有实例都访问同一个锁协调器才能保证互斥性。这种协调器可以是 Redis、ZooKeeper、etcd 等而 Redisson 就是基于 Redis 实现的分布式锁框架。1.2 什么样的业务需要分布式锁不是所有场景都需要分布式锁这个先明确。比如那种允许超卖一点的活动秒杀可能直接用乐观锁加库存字段判断就搞定了根本不用上锁。需要分布式锁的场景通常有几个特征资源是共享的、多个实例都会操作、强一致性要求高。最常见的包括库存扣减、订单号生成防止重复、定时任务幂等控制多个实例同时触发批量任务、用户防重复提交、支付回调处理、活动领券资格校验等。这些场景有个共同点一旦并发冲突出现了后果不是数据错误就是用户收到影响所以必须保证“同一时刻只有一个执行者”。我之前做活动系统时用户领券接口因为重复点击导致同一张优惠券被发放两次排查下来就是没加分布式锁。后来在领券入口加了一把基于用户 ID 的 Redisson 锁问题立刻消失。这个案例让我意识到加锁的位置和锁的粒度比锁的实现方式本身还要关键。1.3 分布式锁需要具备哪些能力从需求出发一个合格的分布式锁至少要满足这几个条件互斥性、可重入、锁超时、阻塞/非阻塞获取、高性能。互斥性是底线同一把锁只能被一个线程持有可重入是指同一线程可以多次获取同一把锁防止业务中嵌套调用把自己锁死锁超时是避免持有锁的线程宕机导致死锁获取方式要灵活有的场景需要阻塞等待有的场景希望拿不到就快速返回性能上不能比业务本身还重否则得不偿失。Redisson 通过封装 RLock 接口把这几项基本都做到了而且对外使用方式和 Java 原生的Lock接口非常相似。换句话讲只要你会用ReentrantLock用 Redisson 几乎没有学习成本。这也是它的易用性口碑来源。2. Redisson 与手写 Redis 锁的差距2.1 SETNX 方案的隐患很多人一提到 Redis 分布式锁第一反应就是SETNX key value。这个方案很简单但是细节非常多。最简单的版本SET lock 1 NX EX 30就能实现加锁和过期时间。首次实现确实快但真正用起来问题不少。第一个问题是不可重入。一个线程多次进入同一把锁时SETNX第二次就不会成功了自己把自己挡在门外。解决方式大概有两种用 ThreadLocal 记录持有状态或者用 Hash 结构记录线程标识和重入次数。第二个问题是锁没有“主人标记”。假设线程 A 的锁因为业务耗时太长自动过期了线程 B 拿到锁后A 执行完用DEL key释放锁误删了 B 的锁。这个问题的标准解法是加一个唯一 Value释放前先GET比对再DEL但GET和DEL不是原子操作中间可能有竞态还得用 Lua 脚本包装。第三个问题是谁来续期锁过期时间设 30 秒但业务耗时 40 秒怎么办要么业务手动续期要么锁干脆崩溃不可用不管怎么做代码都会变成一团乱麻。这些问题不是不能解决只是自己写的时候容易漏。生产环境里我见过几次比较恶劣的事故都是手写锁时忘了唯一标识或者忘了加过期时间导致整个 Redis 实例被锁死或者数据被覆盖。2.2 Redisson 解决了哪些问题Redisson 把上面这些细节都封装了。加锁时它会自动生成一个唯一的标识UUID 线程 ID写入 Redis 的是 Hash 结构key 是锁名称field 是唯一标识value 是重入计数。加锁和解锁都通过 Lua 脚本保证原子性。可重入问题通过 Hash 的value自增来实现同一个线程重复加锁时计数每次都加一解锁时减一减到 0 才真正删除 Key。锁超时和续期问题通过 WatchDog 机制解决默认情况下如果锁的租约时间没有手动指定Redisson 会给锁自动续期默认每 10 秒续一次把过期时间维持在 30 秒。这样既不会因为业务耗时长而死锁也不会因为忘设超时时间而导致锁永久存在。《如果你没有指定leaseTimeRedisson 会开启 WatchDog如果你指定了WatchDog 就不会启动。这一点很容易记混面试也爱问。》2.3 加锁和释放锁的底层脚本Redisson 加锁内部执行的是 Lua 脚本核心逻辑大概是先判断锁是否存在如果不存在就直接记录线程 ID 并设置过期时间如果存在则判断 Hash 中的 field 是否为当前线程。是则执行重入计数加一并刷新过期时间否则直接返回重试。释放锁时也通过 Lua 脚本先校验字段是否属于当前线程不属于就直接返回失败属于再进行计数减一操作如果计数归零就删除整个 Key。整个过程没有任何读改写间隙多个操作都在 Redis 服务端原子执行。这也意味着即使并发极端高不同线程之间也不可能中断 Lua 脚本的执行。3. Spring Boot 项目里接入 Redisson 的完整步骤3.1 依赖引入和配置接入 Redisson 的方式有很多最常见的是在 Spring Boot 项目里用redisson-spring-boot-starter。以 Spring Boot 2.x 为例添加依赖后在application.yml里配置 Redis 连接信息即可。如果想更灵活一点也可以手动把 RedissonClient 注册为 Bean。yml 配置可以这样做spring: data: redis: host: 127.0.0.1 port: 6379 password: database: 0用 Redisson 独立配置的话推荐单独写一个配置类。因为 spring-boot-starter 自动配置有时候会和你自己的 RedisTemplate 配置打架独立配置反而清爽、可控。配置类只需把 Config 构建出来再交给 Spring 管理就够用。Configuration public class RedissonConfig { Bean public RedissonClient redissonClient() { Config config new Config(); config.useSingleServer() .setAddress(redis://127.0.0.1:6379) .setPassword() .setDatabase(0); return Redisson.create(config); } }如果你用的 Redis 是主从或集群模式配置方式会不一样。主从架构用useMasterSlaveServers集群架构用useClusterServers哨兵模式用useSentinelServers。不同模式的故障转移逻辑不同生产环境选型时要先想清楚。3.2 一段可运行的加锁代码拿到 RedissonClient 之后获取锁非常简便。getLock(order:pay: orderId)就得到一个 RLock 实例。使用方式如下Resource private RedissonClient redissonClient; public void payOrder(Long orderId, Long userId) { String lockKey order:pay: orderId; RLock lock redissonClient.getLock(lockKey); boolean locked false; try { locked lock.tryLock(3, 10, TimeUnit.SECONDS); if (!locked) { throw new BusinessException(当前订单正在处理中请稍后再试); } // 业务逻辑支付回调、订单状态变更等 } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusinessException(获取锁被打断请重试); } finally { if (locked) { lock.unlock(); } } }这段代码有几个细节值得强调。tryLock的第一个参数waitTime表示等待获取锁的超时时间超过这个时间还没拿到就返回 false不是无限等。第二个参数leaseTime表示锁的自动释放时间超过这个时间锁会自动释放防止线程崩溃造成死锁。如果你不传leaseTimeRedisson 会启动 WatchDog 自动续期所以如果是长期执行的任务建议不要传第二个参数或者传一个比任务预期执行时间大的值。3.3 加锁操作的取舍旗帜模式也有讲究获取锁的写法有三种lock()、tryLock()、lockInterruptibly()。它们的区别在于获取不到锁时的表现。lock()会一直阻塞等待直到拿到锁这在某些不需要快速失败的批处理任务里很合适tryLock(waitTime, leaseTime, unit)在等不到时会返回 false适合接口型场景lockInterruptibly()在阻塞期间可以被中断适合能响应取消信号的场景。接口型业务我基本都用tryLock因为这个等待时间可以间接起到防重和快速失败的作用。假如把等待时间设成 5 秒代表 5 秒内还没拿到锁就直接告诉用户“系统繁忙”体验上完全能接受。4. Redisson 分布式锁的核心原理4.1 Hash 结构与可重入Redisson 的锁 Key 在 Redis 中存储的是一个 Hash 结构。锁的名称是整个 KeyHash 里的 field 是客户端生成的唯一令牌UUID 线程 IDvalue 是重入次数。第一次加锁时写入 field 并设 value 为 1再次加锁时 value 变成 2以此类推。这种设计的巧妙之处在于它把一个 Java 线程对应到 Redis 的一个 field 上。不同线程即使拿到同一个锁对象field 也不同所以互不干扰。同一个线程嵌套加锁field 相同通过计数累加实现可重入。之前有人在网上问“Redisson 是怎么实现可重入的”其实答案一直都写在 Lua 脚本里。4.2 WatchDog 续期机制Redisson 的 WatchDog 是分布式锁里最有价值的机制之一。默认情况下当你不指定锁的leaseTime时Redisson 会把锁的过期时间设为 30 秒同时启动一个后台定时任务每 10 秒检查一次锁是否还在持有状态。如果还在持有就通过 Lua 脚本把过期时间重置回 30 秒。这个机制的作用是把“业务执行长”和“锁自动释放”之间的矛盾解决掉了。假设你的业务方法执行 40 秒锁自动释放时间是 30 秒如果没有续期第 31 秒时锁就释放了另一个线程就能进入临界区这显然是不安全的。有了 WatchDog只要当前线程还活着且锁没被手动释放它就一直续期锁的生命周期跟业务生命周期基本一致。任务执行完unlock()一调用后台的续期任务也会被主动取消。面试时经常追问一句“WatchDog 默认续期多久”它的默认锁超时时间是 30 秒续期周期是 10 秒即每 10 秒将锁的有效期重置为 30 秒。这个概念不要回答成“无限续期”因为线程业务结束或者 Redisson 客户端宕机之后锁最终还是会释放的。4.3 Lua 脚本与原子性Redisson 加锁的原子性是通过将多个 Redis 操作放在一段 Lua 脚本中一次性执行来保证的。为什么这么设计因为如果先执行一个SETNX判断再执行一个EXPIRE中间一旦出现异常锁就可能变成永久锁或者无效锁。而 Lua 脚本在 Redis 服务端是原子执行的不存在中间状态。举个例子解锁操作包含了三个步骤判断锁是否存在、判断 field 是否属于当前线程、决定是减计数还是删除 Key。如果用 Java 代码分别调用这三步中间可能就被其他线程的操作插入。改成 Lua 脚本之后Redis 会把整段脚本作为一个整体执行中间不会穿插其他命令。《我自己排查过一次非常隐蔽的问题某个接口偶发获取到锁后立刻释放失败。后来发现根本不是 Redisson 的问题而是代码里在 finally 块中调了两次 unlock。Redisson 对重复释放校验比较严格第二次释放时会直接报错。》4.4 多模式下的锁实现Redisson 的锁家族并不仅仅有getLock。它还有公平锁getFairLock、读写锁getReadWriteLock、信号量getSemaphore、闭锁getCountDownLatch。公平锁和普通锁的区别在于它维护了一个等待队列按请求顺序分配锁适合需要公平调度的场景。读写锁允许多个读操作并行写操作独占适合读多写少的资源。实际项目里用到最多的是getLock和getReadWriteLock。缓存刷新场景我常用读写锁读操作不加互斥写操作刷新缓存时把所有读都挡在外面避免读到半新半旧的数据。如果你也遇到这类场景可以考虑用RReadWriteLock而不是普通锁吞吐量会好看很多。信号量用在限流和资源数量控制的场景比较多比如限制某个接口同时最多只允许 3 个请求执行。闭锁则类似CountDownLatch适合协调多个实例之间的任务协作不过这种场景用消息队列可能比分布式锁更合适使用前最好评估一下。5. 实战中的几个典型问题与排查技巧5.1 加了锁但没有锁住业务有时候你以为加了锁结果多个实例同时进入了临界区。排查思路通常是先确认lockKey是否在所有实例上一致。比如用订单号做 Key前后端传值不一致就会导致不同请求落入不同锁段。其次是确认加锁的代码是否覆盖了所有入口比如接口和 MQ 消费各自用了一套锁逻辑那显然互斥是失效的。我之前遇到过一个问题同一个方法在网关层做了签名校验直接返回了缓存数据根本没走到加锁逻辑。多个实例同时读到缓存后同时去扣库存导致超卖。这类问题的本质不是锁实现的问题而是锁的范围没覆盖到完整的关键路径。5.2 死锁还是锁得不到释放如果线上 Redis 里锁 Key 长期存在说明大概率是锁没有被释放。原因可能有三种第一种是业务代码抛了异常但 finally 块里没有 unlock第二种是解锁时因为锁已经过期自己早就释放了finally 里再调 unlock 抛异常第三种是业务内部开启了另一个线程去解锁导致持有线程标识不一致。第三种情况最容易迷惑人。Redisson 加锁时会把当前线程的标识写入 Redis如果在业务代码里用了线程池异步执行然后在子线程里调用unlock()那这个子线程并不持有锁就会解锁失败。如果你必须在主线程加锁子线程处理业务建议把锁对象传给子线程并由子线程释放但这样又容易破坏锁的语义最稳妥的方案还是把锁的生命周期限制在同一个线程内。5.3 锁过期时间设置不合理tryLock(3, 10, TimeUnit.SECONDS)里的 10 秒是租约时间。如果业务执行经常超过 10 秒锁就提前释放了其他线程就会趁虚而入。很多人以为设置了leaseTime就万事大吉实际上要考虑业务的最长执行时间。我的习惯是如果不能准确评估最长耗时就不要传leaseTime让 WatchDog 自动续期如果必须传也要留出足够的缓冲。还有一种情况是业务执行确实短但 Redis 主从切换导致的锁丢失。这个属于分布式锁在 Redis 架构下的理论短板。Redisson 针对这个问题提供了RedLockgetRedLock多节点锁方案通过多数派加锁来提升容错性。但 RedLock 本身也有争议生产用到的比例不高。如果你的业务场景对锁的可靠性要求极高可能要认真评估是否该换用 ZooKeeper 或 etcd 实现。5.4 锁粒度太粗导致吞吐量骤降分布式锁不是越保险越好锁的粒度直接影响系统的吞吐能力。拿秒杀库存举例如果用同一个全局锁去保护所有商品的库存扣减所有商品的请求都会排队等一把锁系统吞吐直接被压垮。合理的做法是按商品 ID 拆分锁 Key比如stock:item:1001这样不同商品的请求互不阻塞只有同一个商品的并发请求才互斥。订单防重也是同理如果所有订单都共用同一把锁那完全没意义应该用订单号作为锁 Key。锁的粒度分析其实需要结合业务实体来设计。之前做抽奖活动时我按用户维度加锁防止同一用户并发抽奖不同用户之间的兑奖请求完全并行性能和互斥都兼顾了。5.5 从日志判断锁的状态排查分布式锁问题时光看代码和 Redis 里的 Key 往往不够日志才是还原现场的关键。建议在加锁、解锁、获取锁失败几个关键位置打上日志包括线程名、锁 Key、等待时间、是否成功等信息。这样可以快速判断是获取锁慢还是业务执行慢或者是锁压根没被释放。我一般会在加锁成功时打印当前线程的锁标识解锁时也打印一次。出现 Redis 锁 Key 残留时通过日志能直接定位到是哪个实例、哪个线程加的最后一把锁再进一步排查那个线程当时执行了什么逻辑。没有日志的情况下排查这种问题基本靠猜效率极低。6. 项目中用好 Redisson 锁的几条实操建议6.1 锁 Key 的设计原则分布式锁的 Key 首先要有业务含义其次要能标识唯一资源。光写inventory_lock这种全局锁就没法支撑高并发而inventory:item: itemId这种带业务维度的 Key 才好用。Key 中建议不要包含太长的字符串尤其是订单号过长时会影响 Redis 存储和网络传输可以折中处理。另外要注意 Key 的字符集尽量统一避免不同环境因为编码差异导致实际 Key 不一致。比如一个服务的实例在 Linux 下另一个在 Windows 下如果拼接字符串时隐含有大小写等差异各自拿到的锁 Key 可能不同互斥自然无效。最简单的办法是统一用常量加参数拼接不要手写拼字符串逻辑。6.2 释放锁时必须放在 finally在 Java 代码里锁的释放必须放在 finally 中这一点怎么强调都不过分。因为业务代码抛异常很常见如果释放语句没在 finally 块中一旦中间抛异常锁就被牢牢占住。虽然锁有过期时间和 WatchDog 续期但这不代表业务上没问题。锁的持有者自己都不知道锁是何时被释放的后续的并发风险是不可控的。如果你使用的是lock.tryLock返回的 boolean 结果那么释放前一定先判断是否拿到了锁。否则你在没有锁的情况下直接unlock()Redisson 会抛出IllegalMonitorStateException异常。之前有同事在tryLock返回 false 后还走到了finally里的unlock()直接导致接口报错排查时找了好一阵才定位到。6.3 高并发下用自旋等待还是快速失败tryLock(waitTime)的等待机制本质上是自旋等待。在 Redisson 中等待期间的底层实现有阻塞和订阅通知机制不会像纯SETNX一样疯狂消耗 CPU但还是会在 Redis 上产生一定的轮询压力。因此等待时间越长Redis 的压力越大。接口型业务建议waitTime不要超过 3 秒且要配合快速失败策略避免所有请求都在等待排名导致线程池耗尽。异步任务型场景可以使用lock()方式一直等待但因业务线程还是会被阻塞如果任务量大且执行时间长调用方可能一直积压请求。实际项目里拉取任务时最好使用限流信号量搭配少量线程而不是无限等待锁。6.4 监控锁的耗时和异常分布运行期间的锁等待耗时直接反映系统并发健康度。建议对加锁成功数、加锁失败数、等待耗时、持锁耗时打点监控做成指标上报。出现异常值时要关注两条线等待耗时高说明并发冲突严重持锁耗时高说明业务临界区执行太慢两者优化方向完全不同。我见过一次比较典型的持锁耗时飙升问题起初怀疑锁本身有问题后来发现是临界区里调了一个外部接口外部服务响应变慢导致锁一直拿在手里。最后通过优化外部调用和缓存预热解决了跟锁本身一点关系都没有。所以锁的监控要和业务链路监控结合才能准确判定瓶颈。6.5 锁的集群部署形态部署形态决定了锁在故障场景下的表现。单机 Redis 是最简单的方案但会单点故障主从模式解决了单点问题但在故障切换的瞬间可能发生锁丢失哨兵模式自动故障恢复但同样存在主从异步复制带来的锁丢失窗口集群模式具备分片和高可用能力但锁 Key 分布在哪个 slot 其实并不受业务控制。Redisson 的多节点锁RedLock是通过向多个独立的 Redis 节点依次加锁来解决容错问题的不止一个 Redisson 官方推荐这种方式。但 RedLock 在学术界和工程界都有不少争议想真正获得强一致性的分布式锁建议调研 ZooKeeper 或 etcd 的方向。这个选型取决于你的业务对一致性的容忍程度。7. 面试题视角这些概念值得专门准备7.1 和 Redis SETNX 手写锁的区别面试官问这个问题本质上想确认你是否理解分布式锁的核心难点。回答时先从底层数据结构和原子性说起手写SETNX很难处理可重入、续期、误删、阻塞等待这些细节而 Redisson 通过 Lua 脚本、Hash 结构、WatchDog 内置这些能力。不要只答“Redisson 是封装好的”要说出具体怎么封装的。另外可以补充一点手写锁时通常需要基于 Spring 的 AOP 或模板方法封装一套注解否则每个业务类都要重复写加锁和解锁逻辑。Redisson 也提供了RLock之类的注解方法但这个功能需要结合标注切面来设置使用时要确认自己的项目里有没有引入相关切面逻辑不要用了注解但没切面生效白等一场。7.2 WatchDog 和 leaseTime 的关系这里有个必踩的坑很多以为tryLock(10, 30, TimeUnit.SECONDS)会自动续期实际上只要传了leaseTimeWatchDog 就不会启动。只有当leaseTime为 -1 时Redisson 才会开启 WatchDog 自动续期。用一句话总结就是“不传租约时间才续期传了就不管了”。如果你想知道当前锁还有多长有效时间可以用remainTimeToLive()方法获取剩余存活毫秒。这在调试锁的生命周期时非常方便。举个例子当锁 Key 在 Redis 里的 TTL 一直不变说明 WatchDog 在持续续期如果 TTL 不断变小说明租约已固定到期就自动释放了。7.3 锁自动续期会带来什么问题WatchDog 允许业务长时间持锁这时候如果业务代码一直没有返回锁就一直被持有其他请求就无法进入。这也算是分布式锁的一个“保护陷阱”业务里出现死循环或外部服务异常会导致锁长期不释放积压大量请求。这种场景靠锁本身无法恢复必须有超时中断机制或者监控告警。所以引入 WatchDog 不是上线之后就什么都不管的我们还需要在业务代码里建立执行时长监控。比如任务执行超过 5 分钟就主动上报告警必要时手动中断任务释放锁。我在做报表批量任务时特地把这个指标接上后来真的发现一个慢查询拖了十几分钟靠告警及时止损了。7.4 Redisson 的锁语义和 Redis 命令的关系理解 Redisson 的原理最终还是要落到 Redis 的命令上。锁的加锁过程主要涉及 EXISTS、HEXISTS、HINCRBY、PEXPIRE 等命令解锁过程涉及 HEXISTS、HINCRBY、DEL 等命令整段逻辑用 Lua 脚本包裹。如果你把这些命令和源码对应起来面试时解释起来就非常有理有据。在实际阅读 Redisson 源码的过程中我发现它内部其实也有一定的配置项比如锁的 watchdog 超时时间可以通过lockWatchdogTimeout来调节。不过这个值不建议一般业务改动。其实大多数时候用默认配置就够了改来改去反而容易引入不确定性。8. 一个完整的用户领券防重案例最后放一个实际案例完整演示 Redisson 分布式锁在防重场景中的使用。背景是营销活动里用户多次点击领券接口并发发生重复发券。优化前我见过数据库中同一用户拥有两条相同优惠券记录因为两个请求都通过了查询校验。优化方式是先按用户维度加锁确保同一用户的请求串行化然后再校验是否已领过。public void receiveCoupon(Long userId, Long couponId) { String lockKey coupon:receive: userId; RLock lock redissonClient.getLock(lockKey); boolean locked false; try { locked lock.tryLock(2, 20, TimeUnit.SECONDS); if (!locked) { throw new BusinessException(操作太频繁请稍后再试); } CouponDO exist couponMapper.selectByUserIdAndCouponId(userId, couponId); if (exist ! null) { throw new BusinessException(你已经领取过该优惠券); } couponMapper.insert(userId, couponId); sendCouponNotify(userId, couponId); } finally { if (locked) { lock.unlock(); } } }这段流程的关键在于先抢锁抢到之后才算进入有效期窗口窗口内做校验和写入。锁保证了同一用户的两次请求不会同时进入校验逻辑从根源上规避了并发重复写。注意锁 Key 用的是 userId而不是整个请求的随机 ID否则同一用户的不同请求之间根本不会互斥。线上压测时我用 100 个线程模拟同一用户并发点击加了锁后数据库里只出现一条优惠券记录没加锁时最多能插入 3 到 4 条问题一目了然。如果你的系统里有类似“同一用户、同一资源、并发操作”的场景这个套路可以直接套用。再说一个大家都容易忽略的点锁 Key 里尽量不要带用户手机号、姓名等敏感信息Redis 是个独立存储任何能看到 Redis 内部数据的人都可能读取到这些信息。用用户 ID 这种内部标识即可既保证了唯一性又不涉及敏感数据。安全合规意识在写锁 Key 时也要同步到位。使用 Redisson 分布式锁这段时间我最深的体会是框架确实省心但使用者的认知水平决定了它在业务中的表现。很多人以为把锁加上就安全了结果锁的粒度不对、释放顺序错乱、超时时间设置得只够买菜最后出了问题还要靠一层层排查找回来。分布式锁不是一个“加上就完事”的调用它需要结合业务并发模型、部署架构、异常恢复能力来综合考虑。如果你刚开始在项目里引入 Redisson建议先小范围试用加上日志和监控确认锁的获取频率和耗时都在合理范围内再逐步推广到核心链路。这样既能享受 Redisson 带来的便利又不至于因为盲目加锁把系统的吞吐拖垮。