Redisson分布式锁从入门到实战先说个真实场景。去年我们有个订单服务做活动零点一过流量直接打满用户疯狂点击提交订单。代码里那套基于数据库UPDATE的库存扣减逻辑瞬间被并发请求打穿超卖了两百多单。事后复盘技术方案其实不复杂——分布式锁没做好。当时用的还是自己封装的一个SETNX加过期时间的Redis锁结果锁过期了线程还没执行完锁被别的线程拿到照样出事。后来我把锁方案整体切到Redisson这才算把这块彻底理顺。Redisson是Java生态里基于Redis实现的一套客户端库它提供的RLock接口用起来和JDK的ReentrantLock几乎一模一样但底层做了很多分布式环境下的细节处理比如自动续期、可重入、公平排队、读写锁隔离这些。这篇文章我不打算讲太多源码重点放在怎么用、为什么这么用、踩过哪些坑尽量让没接触过Redisson的人也能跟着落地。适合谁看如果你的项目在用Redis并且需要处理库存扣减、防重复提交、缓存穿透这类并发问题这篇内容可以帮你节省不少试错时间。1. 项目整体设计与场景拆解1.1 分布式锁到底解决什么问题先明确一个边界单机环境下多个线程共享一份资源靠synchronized或者Lock就能搞定。但服务一旦部署多份比如订单服务开了三个实例前面挂了Nginx负载均衡一个请求可能落到机器A另一个请求落到机器B这时候JVM级别的锁就完全不生效了——每个Java进程的锁只能锁住自己进程里的线程。分布式锁的思路是把“锁的状态”放到一个所有实例都能访问到的第三方组件上比如Redis。所有实例抢锁的时候都去Redis里写同一个key谁写成功谁就拿到锁干完活再把key删掉释放锁。但“用Redis做锁”这件事坑比大多数人想象的多。早期网上的方案是SETNX key value加一个过期时间简单粗暴却存在几个非常经典的问题第一个问题是锁没有自动续期。如果业务代码执行时间超过了锁的过期时间锁自动释放了另一个线程就能抢到这把锁两个线程同时操作同一份数据超卖就出现了。第二个问题是误删锁。线程A执行时间过长锁过期被线程B抢到A执行完后执行DEL命令直接把B的锁删掉了B还没执行完C又进来了锁形同虚设。第三个问题是不可重入。同一个线程在嵌套方法里再次获取同一把锁会把自己锁死。Redisson其实也没有发明什么新东西它做的就是把上面这些破事都处理好了。你只需要调一个lock()方法剩下的续期、可重入、防误删它全帮你搞定。1.2 Redisson的关键特性和选型理由我当初在技术选型时也纠结过要不要直接上ZooKeeper的锁ZooKeeper的分布式锁通过临时顺序节点实现可靠性确实高不存在锁过期问题但随之而来的是运维成本高、性能比Redis差不少还要额外部署一套中间件。Redis分布式锁性能极高单线程模型处理简单命令都在微秒级别而且公司本来就有Redis集群完全不用新增基础设施。Redisson则是Redis分布式锁领域里最成熟的实现。它有这几个让我觉得“值回票价”的特性一是看门狗机制。默认情况下Redisson获取锁后如果业务没执行完看门狗会每10秒自动给锁续期一次把过期时间维持在30秒。这样就不会出现“锁自动到期业务还没跑完”的尴尬。二是可重入锁。同一个线程可以多次获取同一把锁内部用计数器记录重入次数释放一次减一次减到零才真正删除锁。三是公平锁。内部通过Redis队列实现请求排队按照请求顺序依次获取锁避免线程饥饿。四是读写锁。RReadWriteLock支持读读并发、读写互斥、写写互斥适合缓存更新这种典型场景。五是红锁RedissonRedLock。如果你对锁的安全性要求极高可以部署多个独立的Redis实例需要过半数的节点都同意加锁才算拿到锁。这解决了主从模式下主节点宕机导致锁丢失的问题。从使用场景看如果你的业务能接受极端情况下的极小概率问题大部分互联网业务都能接受单节点RedisRedisson就够了。如果涉及金融类资金操作那就要考虑红锁或者ZooKeeper方案。1.3 一句话概括整体架构从整体架构来看这套方案的链路是业务代码调用RLock接口Redisson客户端通过Lua脚本原子化地执行“判断key是否存在、写入hash结构、设置过期时间”这组操作锁的元数据存储在Redis的一个Hash结构里key是锁名称field是线程唯一标识value是重入次数。释放锁时再通过Lua脚本原子化地做“判断持有者、递减计数、删除key”。看门狗则在后台异步续期。所以你在业务代码里看到的可能只是两三行Java代码但底层每一步都经过精心设计这也是为什么我建议项目里直接用成熟组件而不是自己造轮子——你根本想不到那么多边界情况。2. 核心细节解析与关键机制2.1 加锁、解锁的底层执行流程先看最基本的用法// 1. 创建Redisson客户端这里以单节点为例 Config config new Config(); config.useSingleServer() .setAddress(redis://127.0.0.1:6379) .setPassword(123456); RedissonClient redisson Redisson.create(config); // 2. 获取锁对象 RLock lock redisson.getLock(order:pay:20240501); // 3. 加锁 lock.lock(); try { // 业务逻辑 doSomething(); } finally { // 4. 解锁 lock.unlock(); }这段代码背后的执行逻辑比表面看起来复杂得多。lock.lock()这一行Redisson底层做的是先通过Lua脚本执行一组判断用EXISTS检查锁key是否存在如果不存在就用HASH结构存储锁信息设置过期时间返回1表示加锁成功如果key已经存在则检查Hash里的field是否是当前线程的唯一标识UUIDThreadId如果是就执行HINCRBY把重入计数加1同时重置过期时间返回1如果field不是当前线程说明锁被其他线程持有返回0表示加锁失败。这个Lua脚本是原子的Redis单线程执行脚本不会存在中间状态被其他命令插队的情况。这也是Redis分布式锁能成立的基石。解锁也类似通过Lua脚本判断当前线程是否锁的持有者如果是才删除key。如果锁已经过期自动释放了线程再来解锁就会返回0或者抛出异常不会误删其他线程的锁。2.2 看门狗机制的细节与注意事项看门狗是Redisson最贴心但也最容易让人误解的功能。默认情况下锁没有指定leaseTime租约时间看门狗会在加锁成功后每10秒执行一次续期把锁的过期时间重置为30秒。这意味着什么意味着只要你的业务线程还活着锁就不会过期。但如果你设置了leaseTime比如lock.lock(10, TimeUnit.SECONDS)看门狗就罢工了锁到了10秒必然释放不管业务跑没跑完。所以有一个很重要的经验不要轻易给lock方法传leaseTime除非你非常确定业务能在这个时间内完成。我们的支付回调处理逻辑比较复杂会调用多个远程服务最慢的时候能跑到15秒以上如果当初拍脑袋设个10秒过期时间锁就成了一块废铁。但看门狗也有它的死角。如果业务线程发生了长时间阻塞比如内部调用的Dubbo接口超时设置了20秒线程卡在RPC等待上看门狗会在Redis里不断续期锁一直不释放。此时其他实例只能干等。解决办法有两个方向一是优化业务代码把大事务拆小尽量缩短持锁时间二是使用tryLock设置一个合理的等待时间超过等待时间就放弃抢锁返回失败避免线程无限堆积。2.3 三种常用锁的适用场景Redisson提供了多种锁类型我实际项目中用得最多的有三种。第一种是普通可重入锁RLock适用于需要互斥访问的短任务比如库存扣减、发放优惠券、防重复下单。这是最基础的锁类型也是默认首选。第二种是公平锁FairLock适用于排队要求严格的场景比如秒杀活动按先到先得的原则处理。普通锁是“大家都去抢抢到算谁的”公平锁则是“按照请求到达顺序依次处理”。公平锁在Redis里的实现是通过一个消息队列加信号量性能比普通锁低一些但能避免线程饥饿。第三种是读写锁RReadWriteLock适用于读多写少的缓存场景。比如配置中心多个线程同时读配置是没有问题的但如果某个线程在更新配置读写线程都必须等待。用读写锁读读操作完全并发性能更高同时又能保证写操作的独占性。再说说红锁。如果你看过Redisson的文档会看到一个RedissonRedLock类。它需要多个独立的Redis主节点加锁时依次向所有节点请求锁只有超过一半的节点返回成功才认为加锁成功。这个方案能解决“主节点宕机导致锁丢失”的问题因为即使某个主节点挂了其他节点仍然持有锁信息。但代价也很明显需要额外的Redis节点加锁耗时更长而且一旦某个节点网络分区或者时钟漂移红锁也可能出问题。业内对红锁的争议一直存在我的建议是不是资金类绝对强一致的场景不推荐上红锁。2.4 关于锁粒度与性能的思考拿到一把锁之后最先要思考的是锁的粒度。锁的粒度越细并发能力越强但实现复杂度越高锁的粒度越粗实现越简单但吞吐量越低。举个例子商城系统里的库存扣减。如果做成全局锁stock:global那么所有商品、所有门店都在抢同一把锁性能会奇差无比。正确做法是做成商品维度锁stock:sku:10001甚至门店加商品维度锁stock:shop:001:sku:10001不同商品之间的扣减互不影响。有一点必须注意锁的key必须选对维度否则会出现严重的安全问题。比如做防重复提交用用户ID加场景做key那同一个用户在不同场景下可以并发提交没问题但如果把用户ID漏了全局key一把锁锁死所有用户系统瞬间就瘫了。3. 实操过程从依赖引入到完整Demo3.1 环境准备与依赖引入我用的Spring Boot项目技术栈是Spring Boot 2.7.x Redisson 3.17.x。引入Redisson的方式很简单dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.17.7/version /dependency引入这个starter之后配置项会通过Spring Boot的自动配置机制加载。在application.yml里做如下配置spring: redis: host: 127.0.0.1 port: 6379 password: 123456 database: 0 data: redis: repositories: enabled: false这里有个小坑redisson-spring-boot-starter虽然会自动读取spring.redis下的配置但如果你同时引入了spring-boot-starter-data-redis两个客户端会各建各的连接配置都要为它们各自准备一份。在这种混合使用的情况下最稳妥的方式是手动创建RedissonClient的Bean而不是依赖自动配置。Configuration public class RedissonConfig { Bean public RedissonClient redissonClient() { Config config new Config(); config.useSingleServer() .setAddress(redis://127.0.0.1:6379) .setPassword(123456) .setConnectionPoolSize(10) .setConnectionMinimumIdleSize(2); return Redisson.create(config); } }3.2 基于注解实现防重复提交看一个实际项目里的用法。我们有个C端接口用户点击“提交订单”按钮由于前端没有做好防抖用户狂点之下可能同时发来多个请求于是我们需要在服务端做幂等控制。实现方式有几种最简单直接的是用Redis的setIfAbsent做短时令牌。但因为订单提交涉及异步流程最稳妥的做法还是用分布式锁锁住“用户加场景”这个维度。于是我们写了个注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface NoRepeatSubmit { long expireTime() default 3; TimeUnit timeUnit() default TimeUnit.SECONDS; }然后配合AOP切面Aspect Component public class NoRepeatSubmitAspect { Autowired private RedissonClient redissonClient; Around(annotation(noRepeatSubmit)) public Object around(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) throws Throwable { // 从请求上下文中获取用户ID伪代码 Long userId SecurityUtils.getUserId(); String lockKey lock:order:submit: userId; RLock lock redissonClient.getLock(lockKey); boolean locked lock.tryLock(0, noRepeatSubmit.expireTime(), noRepeatSubmit.timeUnit()); if (!locked) { throw new BusinessException(ErrorCode.REPEAT_SUBMIT, 提交过于频繁请稍后再试); } try { return joinPoint.proceed(); } finally { // 如果当前线程还持有锁才释放 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } }看到tryLock(0, expireTime, timeUnit)这个方法有三个参数第一个参数是等待时间这里设为0表示拿不到锁就直接放弃不排队傻等第二个参数是锁的过期时间第三个是时间单位。这里明确传入了过期时间看门狗就不参与续期了。对防重复提交这种场景来说是合理的3秒钟内重复提交都算频率异常即使业务在极端情况下跑到3秒后也接近系统能容忍的极限了。这里还有一个技巧lock.isHeldByCurrentThread()这个判断很重要。因为如果锁在持锁期间自动过期了再调unlock()会抛出IllegalMonitorStateException这个判断可以避免无谓的异常。不过要注意这个判断在锁已经自动过期的情况下返回的是false但如果在解锁前锁被别的线程抢走了它也返回false此时就不能再执行解锁了——所以这个判断是解锁操作前的安全兜底。3.3 库存扣减的完整实战Demo防重复提交还好理解库存扣减这种涉及金钱和数据一致性的场景才是分布式锁真正发挥价值的地方。扣减库存的接口逻辑大概是这样的前端传商品ID和数量后端判断库存是否足够如果足够就扣减同时更新已售数量。如果不用分布式锁并发请求下很容易出现两个线程同时读到库存100同时判断库存足够同时扣减最后库存变成99而不是98超卖就发生了。基于Redisson的正确做法public void deductStock(Long skuId, Integer count) { String lockKey lock:stock:sku: skuId; RLock lock redissonClient.getLock(lockKey); // 等待3秒拿锁拿不到就快速失败 boolean locked lock.tryLock(3, 10, TimeUnit.SECONDS); if (!locked) { throw new BusinessException(系统繁忙请稍后重试); } try { // 1. 查询库存 Integer stock stockMapper.selectStockBySkuId(skuId); // 2. 校验库存 if (stock count) { throw new BusinessException(库存不足); } // 3. 扣减库存 stockMapper.decrementStock(skuId, count); // 4. 后续可能还有更新缓存、记录流水等操作 } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }这段代码看起来就清爽多了。tryLock等待3秒拿锁超过3秒直接报“系统繁忙”不会造成线程无限堆积。业务在10秒内执行完锁自动释放如果执行不完看门狗会续期。这里有个细节值得深究tryLock(3, 10, TimeUnit.SECONDS)里第二个参数10秒是锁的过期时间。和前面lock()方法不同一旦显式指定leaseTime看门狗就不会续期了。所以这里的10秒是业务执行时间的上限。如果你的扣减逻辑里还调了第三方接口或者涉及多个表的更新这个值要设得宽裕一些。我们线上环境根据压测结果大部分扣减请求在800毫秒内完成取10秒已经非常安全。3.4 调用方的兜底策略与异步化在压测过程中我们进一步发现如果所有请求都先排队抢锁再依次扣库存锁处理的并发能力还是不够。比如秒杀商品只有100件瞬间来了5万请求虽然有锁保护不会超卖但5万个请求都要走一遍“抢锁-查库存-扣减”的完整流程数据库压力扛不住锁的等待时间也会很夸张。最终的优化方案是同步接口只做校验扣减操作异步化。用户提交秒杀请求后先把请求打到队列里我们用RabbitMQ接口立刻返回“请求已受理”后台消费者依次处理队列里的请求走分布式锁加扣减逻辑。这样同步链路几乎没有压力队列天然帮我们做了削峰填谷锁也能集中处理真正有效的请求。这引出一个更普适的经验分布式锁是保障一致性的手段不是提升性能的方案。真正的高并发场景应该从架构层面规避并发——消息队列串行化、批量合并、缓存预扣减都是可以考虑的方向。锁的粒度、等待时间、过期时间的设计都要和整体流量模型匹配而不是简单地加一把锁就完事。3.5 Lua脚本实现库存扣减极致优化再说一个进阶优化。如果扣库存只是简单的数字扣减完全没必要用分布式锁Redis的Lua脚本本身就是原子操作。一个DECRBY命令在Redis单线程模型下就是原子性的哪来的超卖问题我项目里有一个热点商品的库存扣减用的是自研的Lua脚本-- 检查库存并扣减 local stock redis.call(GET, KEYS[1]) if not stock then return -1 -- 库存不存在 end if tonumber(stock) tonumber(ARGV[1]) then return 0 -- 库存不足 end redis.call(DECRBY, KEYS[1], ARGV[1]) return 1 -- 扣减成功用Spring Data Redis的DefaultRedisScript执行这个脚本单次操作消耗也就是毫秒级别。这种方案对比分布式锁的优势是不需要加锁、排队、解锁的完整流程性能极高尤其适合库存量很大、扣减频率高的场景。但脚本方案也有局限如果扣库存后还需要写数据库订单表、更新用户积分、记录流水这些操作没法在Redis脚本里完成还是要回到分布式锁或者本地事务方案。所以Lua脚本和分布式锁之间不是替代关系而是互补关系。判断标准很简单操作是否可以被限制在Redis内完成如果能脚本优先如果不能锁优先。4. 常见问题与排查思路实录4.1 加锁后没释放导致死锁在项目初期团队里有同事写代码时只在try块结束后调了lock()忘记写unlock()结果锁一直不被释放。直到其他线程全部卡在抢锁环节接口大量超时我们才通过监控发现锁的key一直存在。排查路径也比较清晰先看Redis里锁key的TTL。正常情况下业务执行完锁key应该被删除如果锁key还在且TTL没有刷新迹象大概率是没释放。再看日志结合代码Review就能快速定位。解决方式很简单永远在finally块里解锁。更严谨一点用tryLock()加超时时间就算代码出问题锁到期也会自动释放不至于造成永久死锁。我看网上有人会把锁key的过期时间设置得很长比如10分钟这在极端故障情况下反而是灾难——Redis里堆积了大量无效锁key还要花精力去清理。过期时间够用就好按业务毛估之后上浮100%作为安全余量就够了。4.2 锁自动过期导致并发问题这个坑属于分布式锁的经典命门。场景是这样的某日凌晨核心服务出现慢SQL一个持锁线程执行时间超过了锁的30秒默认过期时间锁自动释放另一个线程马上抢到了锁两个线程在同一时刻都对同一笔订单进行了操作最终订单状态被覆盖出错。这类问题最初的排查方向很容易跑偏会怀疑是不是AOP顺序问题、是不是锁key配错。但根本原因就是锁过期。处理方式优先缩小持锁时间——把锁内的逻辑拆出去只保留真正需要互斥的部分。比如扣库存的同时还调了会员服务和优惠券服务其实这些非核心流程可以移到锁外执行先扣库存再异步发消息处理其他逻辑。还有一种规避思路是不要用默认的30秒过期时间显式指定一个更合理的leaseTime。针对慢SQL这类异常情况建议代码里加一个防御开关监控持锁时间超过阈值的请求实时告警。4.3 Redisson和Lettuce冲突怎么解决我遇到过这样一个故障项目同时引入了spring-boot-starter-data-redis和redisson-spring-boot-starter启动的时候没问题但运行一段时间后连接池报错一堆RedisConnectionException。后来排查发现是连接管理冲突。Spring Boot 2.x默认用Lettuce作为Redis客户端而Redisson用的是Netty连接管理如果两者都创建了连接池并且配置项互相覆盖就会出现资源竞争。解决办法是我们最终手动声明了RedissonClient的Bean并且把spring.autoconfigure.excludeorg.redisson.spring.starter.RedissonAutoConfiguration排除掉让Redisson不参与自动配置。这样项目的RedisTemplate用Lettuce分布式锁用Redisson各自管理自己的连接池互不干扰。4.4 主从切换导致的锁丢失风险再聊一个比较隐蔽的问题如果你的Redis是主从架构主节点负责写从节点负责读。当加锁请求到达主节点主节点还没来得及把数据同步到从节点主节点挂了Redis集群自动把从节点提升为主节点。此时原来的锁数据在新主节点上是不存在的其他线程可以畅通无阻地加锁成功。之前持有锁的线程还在执行业务并发问题由此产生。这个问题有几种应对策略一是使用Redlock多节点加锁即便某个节点故障其他节点仍然保有锁信息二是在业务层面容忍极小概率的超卖或重复操作同时用数据库的唯一约束兜底三是用ZooKeeper替代Redis做锁强一致模型下不存在锁丢失问题但要接受性能下降的事实。我的建议是绝大多数互联网业务场景选第二种。超卖几单可以退款补偿但系统性能和响应时间不行。金融级别的强一致场景老老实实引入强一致中间件别用Redis做锁。4.5 常见问题排查速查表整理一个速查表方便工作中快速定位问题现象可能原因排查方法解决方案接口大量超时Redis锁key一直存在代码未释放锁/释放锁失败查看Redis锁key的TTL搜索lock:前缀使用tryLock超时时间finally块释放锁业务执行时间超过锁过期时间出现并发锁过期时间设置太短查看监控持锁时间是否超过leaseTime调大leaseTime或使用无参lock()启用看门狗IllegalMonitorStateException异常锁已被自动释放当前线程再去解锁查看异常栈解锁前先判断isHeldByCurrentThread()加锁等待时间过长线程堆积waitTime设置过长/锁持有时间过长查看线程堆积数量和耗时分布缩短waitTime锁内逻辑拆分异步化改造项目同时有Lettuce报连接错误两个Redis客户端连接池冲突检查配置确认是否两者都创建了连接手动配置RedissonClient排除自动配置在排查分布式锁相关问题时有一个通用原则先确认锁的key是否按预期存在和释放再确认持锁时间是否在合理范围内最后确认锁的粒度是否符合业务模型。把这些基础项过完绝大部分问题都能定位到根因。5. 进阶技巧与日常开发中容易忽略的细节5.1 不要让锁内逻辑膨胀分布式锁虽然在数据一致性上做了保障但代价是并发度下降。锁内逻辑越多、执行时间越长其他线程等待时间就越长系统吞吐量就越低。这个约束本质上是在逼你思考真正需要互斥的到底是什么举个例子创建订单时需要校验用户是否有资格、校验库存、生成订单号、扣减库存、更新优惠券、发送通知。每一步都很重要但真正需要锁保护的只有“校验库存扣减库存”那一小段。用户是否是否有资格在锁外查缓存就行发送通知可以放到MQ里异步处理。如果整段逻辑全部框进锁内等于把所有串行化的开销都吃进来了性能会非常难看。所以设计锁内逻辑时我习惯自问三个问题这段代码是否操作了共享资源是否必须在该时刻独占能不能异步或者延迟处理回答完这三个问题锁内代码量通常会腰斩。5.2 注意线程与锁的绑定关系Redisson的锁和线程是绑定的这个语义在使用Async异步方法时容易被忽略。假设你在主线程里获取了锁然后调用一个Async方法去释放锁这就会出问题。Redisson判断锁持有者是靠“UUID线程ID”的组合异步方法的线程ID不同它不认为自己是锁的持有者解锁会失败。这也是为什么我在写解锁代码时加isHeldByCurrentThread()判断的一部分原因——表面上是避免异常实际上也是在防止误调。如果你确实需要跨线程传锁Redisson官方不推荐这么做更合理的做法是不要让锁跨线程把获取和释放放在同一个线程内。5.3 分布式锁方案的监控与治理项目上了Redis锁以后不能只停留在“能锁住”的层级还要建立可观测的机制。我们线上对每个业务锁做了埋点统计加锁耗时、持锁时间、等待时间、加锁失败率。刚开始上锁时这里的数据完全出乎意料——某个活动页的接口平均持锁时间只有20毫秒但P99持锁时间高达1.2秒说明存在极少数慢SQL把持锁时间拉长了。把这类数据暴露出来之后我们对慢SQL做了优化P99持锁时间降到了200毫秒以内。还有一个有价值的指标是锁等待超时次数如果这个数字很高说明锁竞争激烈要么是锁粒度太粗要么是业务流量超过了系统处理能力该做异步化改造了。建议做分布式锁时顺便接上Prometheus把下面几个指标埋进去锁获取等待时间、持锁时间、获取失败次数、业务锁数量。有了这些数据你才能知道锁是不是健康系统有没有隐患。空有一把锁没有监控等于开了车不看仪表盘。5.4 理解并善用SPEL表达式动态生成锁Key在复杂业务里锁的key经常是动态拼出来的。比如订单号、用户ID、店铺ID都可能作为锁维度的组成部分。如果用AOP处理锁逻辑可以考虑支持Spring的SPEL表达式来解析锁key。简单思路是注解里写锁key模板比如order: #orderId :user: #userId在切面里通过SpelExpressionParser解析方法参数的名称和值动态生成最终的锁key。这样代码会非常清爽业务方只需要加注解锁的逻辑完全收拢到切面里。这里有一个要注意的细节SPEL表达式如果解析失败比如方法参数名没有通过-parameters编译参数保留下来表达式解析就会拿不到参数名返回null。所以需要在编译插件里配置parameterstrue/parameters否则这种方案会踩坑。6. 结语从锁本身思考并发方案的选择文章写到这里想再聊聊我个人的体会。Redisson分布式锁只是一个工具它解决的是一个非常具体的问题多个节点上的多个线程在同一时间去争抢同一份共享资源时如何保证这份资源只被一个线程操作。它好用、强大自带看门狗和可重入机制但仍然只是一个互斥手段不是万能的。在实际项目里我一个很大的体会是能用数据本身保证一致性的尽量不要依赖锁能用消息队列串行化的尽量不要在同步接口里去抢锁能用Lua脚本在Redis内部原子性完成的尽量不要引入额外的锁组件。锁是并发场景下的兜底方案不是第一方案。比如库存扣减热点商品的库存完全可以先预加载到Redis用Lua脚本原子扣减最后异步同步回数据库。再比如防重复提交如果前端做了按钮置灰后端再配合幂等表做唯一约束大部分场景根本不需要分布式锁。只有业务逻辑绕不开、必须跨服务或者跨实例独占资源时才轮到Redisson出场。回过头来看我们那次订单超卖的事故根源其实不在技术选型而在于当时拿到需求时完全没考虑多实例部署下的并发一致性问题。如果早点把分布式锁纳入方案设计而不是等流量把系统打挂之后再补救整个上线过程会省心得多。如果你正在做类似的并发改造建议按照这个顺序走一遍先梳理业务流程中真正需要互斥的操作再确定锁的维度是用户维度、订单维度还是商品维度然后选择锁的类型普通锁、公平锁还是读写锁最后设计超时时间和监控指标。这样一步一步走下去分布式锁才能真正发挥它该有的作用而不是成为下一个生产事故的导火索。