
一个SpringBoot项目里多个服务节点同时扣库存结果超卖了定时任务在集群环境下重复执行数据被跑了两遍用户重复点击提交按钮订单被创建了好几条。这些场景我猜你大概率遇到过或者正在被困扰。解决这类问题的常见方案就是分布式锁而在Java生态里Redisson是实现在分布式锁最主流的工具之一它基于Redis用起来简单但背后的可重入锁、读写锁机制如果理解不透线上出问题的时候排查起来会非常痛苦。这篇文章我会结合我自己的项目经验把Redisson分布式锁从“为什么需要”到“怎么用对”再到“底层机制怎么跑通的”完整拆一遍。适合刚接触分布式锁的SpringBoot开发者也适合用了Redisson但没深究过原理、想系统梳理一下的同学。看完你至少能搞明白三件事第一分布式锁到底解决了什么问题第二Redisson的锁和Redis原生命令有什么区别好在哪里第三读写锁、可重入锁各自的应用场景和实现思路踩过哪些坑。1. 为什么单机锁不够用了分布式锁的核心痛点1.1 从synchronized到分布式锁的演进逻辑在单体应用时代JVM内置的synchronized和Lock接口基本能搞定所有并发问题。它们的原理是依托JVM内存模型通过Monitor监视器机制保证同一时间只有一个线程能进入临界区。但到了微服务架构下业务方法被部署在多个Tomcat实例中每个实例都是一个独立的JVM进程synchronized锁的粒度仅限于单个JVM内部。A服务节点上的线程拿到了锁B服务节点上的线程根本不知道这回事照样能同时去操作共享资源。这里我给个更形象的比喻单机锁就像一扇门一次只能进一个人但前提是所有人都得从这扇门进分布式环境下楼有多个入口每个入口都有一扇独立的门你只锁住其中一扇其他入口照样进人进出秩序就乱了。分布式锁的本质就是把“一把锁”从进程内部搬到多个进程共享的外部存储上让所有节点都去同一个地方抢锁。谁抢到了谁就获得了操作临界资源的资格。这个共享存储可以选数据库、ZooKeeper、etcd也可以选RedisRedisson就是基于Redis实现的分布式锁框架。1.2 分布式锁必须具备的四个硬性条件一个合格的分布式锁至少要满足以下四个条件缺一个线上都可能出事故互斥性任意时刻只有一个客户端能持有锁。这是分布式锁最基础的要求做不到互斥锁就失去了意义。防死锁持有锁的客户端宕机或者网络异常锁必须能自动释放不能一直卡在那里。Redis中给锁设置过期时间TTL就是为了解决这个问题。可重入同一个客户端在持有锁的情况下再次请求同一把锁应该能成功。因为业务逻辑往往存在方法嵌套调用比如一个方法加了锁它内部又调用了另一个也加了同一把锁的方法如果不能重入自己就把自己锁死了。高性能与高可用获取锁和释放锁的操作要够快不能严重影响业务响应时间。同时锁服务本身不能成为单点Redis主从切换等场景需要考虑。1.3 常见的业务应用场景盘点结合我接触过的项目分布式锁最典型的使用场景集中在下面几类库存扣减秒杀、下单场景下多个服务节点同时扣减同一个商品的库存需要用分布式锁保证扣减操作的原子性。防重复提交用户快速双击提交订单或者支付回调与前端请求并发触发需要保证幂等。定时任务调度在集群环境中同一个定时任务会在每个节点上都触发。为了避免重复执行通常会抢一把分布式锁抢到锁的节点执行其他节点直接跳过。常见的xxl-job这类框架内部也依赖了类似机制。数据迁移与缓存刷新比如多节点同时刷新一个大缓存或者在多个实例上做数据迁移都希望只有一个节点在干这个活。分布式事务补偿某些场景下需要保证多个服务之间状态变更的一致性分布式锁可以用来控制状态机的流转防止状态被并发覆盖。2. Redisson是什么它凭什么比原生SETNX更好用2.1 Redisson在Redis生态中的定位Redisson是一个基于Java的Redis客户端但它的定位远不止是“连接Redis的SDK”。普通的Jedis和Lettuce更侧重于提供底层的Redis操作命令而Redisson提供的是大量封装好的分布式数据结构和服务比如分布式锁、分布式原子类、分布式队列、分布式Map等。可以理解成Jedis给你提供的是砖头和水泥Redisson直接帮你砌好了墙和房间。在分布式锁方面Redisson最有价值的一点是它把“获取锁”“续期”“释放锁”“等待锁”这一整套复杂的流程封装成了一个简单的Java接口。你不需要关心底层Redis命令是怎么组合的也不需要自己处理各种异常边界使用体验和本地JDK的ReentrantLock几乎一致。2.2 原生SETNX方案常见的三个问题在Redisson普及之前很多团队是自己封一套SETNX EXPIRE的方案实现分布式锁。思路是这样的先SETNX lockKey value成功表示抢到锁然后EXPIRE lockKey 10设置过期时间防止死锁用完之后DEL lockKey释放锁。这套流程简单但有几个非常隐蔽的坑SETNX和EXPIRE不是原子的如果SETNX成功之后线程在设置EXPIRE之前挂掉了锁就永远不会过期直接变成死锁。当然可以通过Lua脚本把两步合成原子操作但这要求编码者有比较高的意识。误删别人的锁线程A持有锁因为业务执行时间较长锁到期自动释放了。这时线程B抢到了锁开始执行但A执行完了它直接DEL lockKey把B的锁删掉了。正确做法是删除前要比较value是否是自己设置的唯一标识但很多团队的初版实现根本没做这一步。锁没有可重入性同一把锁在嵌套方法里再次获取直接就被阻塞死等必须自己实现重入计数逻辑。上面每一个问题在低并发场景下可能都不会暴露但一旦碰上流量高峰全是事故隐患。Redisson正是把这些问题从框架层面解决了。2.3 看门狗机制Redisson相比SETNX最大的优势Redisson解决“业务没执行完锁却被释放”的核心机制是看门狗Watchdog。默认情况下Redisson获取锁时会指定一个leaseTime租约时间如果不显式指定默认是30秒。但Redisson并不是“死等”这30秒它在成功获取锁之后会启动一个后台线程定时检查只要锁还持有在自己手上它就会每隔10秒leaseTime的三分之一自动把锁的过期时间重置为30秒。打个比方你租了一间房约定30天到期。但房东安排了一个管家每天都会来看一眼只要你还住着他就自动帮你把租期续到30天后。这样即使你的业务方法执行了很长时间锁也不会因为超时被提前释放。只有当客户端宕机或者网络断了后台续期线程随之停止锁才在最长30秒后自动释放不影响其他客户端获取。这个机制对比手动设置固定过期时间的方案优雅太多。你用原生SETNX时过期时间设短了高并发下大流量业务可能还没跑完锁就没了设长了一旦持有锁的节点挂了其他节点就要干等很长时间。看门狗等于把这个问题从“误差不可避免”变成“自动动态校准”。3. SpringBoot项目集成Redisson的完整实践3.1 依赖引入与参数配置用Maven构建SpringBoot项目时引入Redisson的方式很简单。需要注意SpringBoot版本和Redisson版本之间的兼容性我用的是SpringBoot 2.7.x对应的Redisson版本是3.17.x运行稳定没遇到问题。如果你用的是SpringBoot 3.x建议选择Redisson 3.20以上的版本以保障对Jakarta命名空间的适配。dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.17.7/version /dependency引入starter之后在application.yml中配置Redis连接信息spring: redis: host: 127.0.0.1 port: 6379 password: database: 0这个starter的好处是会自动装配RedissonClient对象你直接在业务代码里Autowired就能用。对于常规开发用这一套配置就足够了。如果涉及Redis集群、主从或者哨兵模式配置方式稍许不同但核心API是一样的。3.2 基础加锁与解锁代码模板一个最简单的Redisson分布式锁使用模板如下。我习惯定义一个常量作为锁的key前缀避免key冲突导致逻辑互相干扰。Autowired private RedissonClient redissonClient; public void deductStock(Long skuId, Integer count) { String lockKey lock:stock: skuId; RLock lock redissonClient.getLock(lockKey); boolean isLocked false; try { // 尝试获取锁最多等待3秒锁自动释放时间默认30秒看门狗自动续期 isLocked lock.tryLock(3, TimeUnit.SECONDS); if (!isLocked) { throw new BusinessException(系统繁忙请稍后重试); } // 业务逻辑检查库存、扣减库存 doDeductStock(skuId, count); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusinessException(加锁过程被中断); } finally { if (isLocked lock.isHeldByCurrentThread()) { lock.unlock(); } } }有几个细节我要单独拿出来说一下这些都是我在代码评审里反复提醒过的点必须使用tryLock而不是locklock()方法是阻塞式的获取不到锁会一直等下去。在高并发场景下这会导致大量线程堆积。tryLock(waitTime, unit)可以控制最大等待时间拿不到锁快速失败配合友好提示用户体验更好。unlock之前要判断isHeldByCurrentThread()防止因为业务执行时间过长锁已经自动过期被其他线程获取当前线程去解锁时把别人的锁释放掉。虽然Redisson的解锁做了owner判断但显式再校验一次更稳妥。finally中释放锁是铁律如果业务逻辑抛出异常锁必须在finally块中释放否则等看门狗自动续期到业务彻底结束锁也要等30秒之后才能被其他线程获取这在长事务场景下会产生明显的阻塞。3.3 场景实战集群环境下防重复执行定时任务分布式锁很常见的一个落地场景是保护定时任务。假设你的应用部署了3个节点用Spring的Scheduled注解写了一个每分钟执行一次的统计任务正常情况下3个节点都会同时触发。如果不加锁统计结果会被重复计算写入数据库时甚至可能因为主键冲突报错。我自己常用的实现方式是封装一个定时任务执行器Component public class OrderStatisticsTask { Autowired private RedissonClient redissonClient; Scheduled(cron 0 0 2 * * ?) public void run() { String lockKey task:order:statistics; RLock lock redissonClient.getLock(lockKey); try { // 抢锁抢不到说明其他节点已经在执行直接放弃本次调度 if (lock.tryLock(0, 30, TimeUnit.SECONDS)) { // 执行统计逻辑 statisticsService.calculateDailyOrderSummary(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error(定时任务加锁异常, e); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } }注意这里的tryLock(0, 30, TimeUnit.SECONDS)第一个参数waitTime传的是0表示拿不到锁立即返回不会等待。这适合定时任务场景因为你不希望每个节点都阻塞等待其他节点已在跑你跳过这一次即可。第二个参数30是leaseTime这里我显式指定了锁的租约时间配合上节说过的看门狗机制。若第二个参数不传调用tryLock(0, TimeUnit.SECONDS)Redisson会启用30秒默认租约并启动看门狗续期。两种写法都可以但显式指定leaseTime可以避免某些特殊情况下看门狗续期线程对生命周期管理造成的干扰更符合定时任务这种“执行时间基本可控”的预期。3.4 项目集成时必须避开的两个坑集成Redisson第一年我在生产环境碰到过两个比较棘手的问题单独拎出来给各位提个醒。第一个坑Redis连接超时导致锁获取失败。在高负载下Redis的响应时间会变长Redisson底层默认的超时时间可能在你的业务响应标准下显得太慢。如果你的Redis服务是单节点它还是整个分布式锁的瓶颈。没有极致的高可用需求可以接受但如果有建议使用Redisson对Redis Cluster或Sentinel模式的原生支持。我见过有团队在业务锁场景用普通单节点Redis主从切换时一下丢了锁瞬间出现并发竞争导致数据错乱排查了很久才发现是切换期间锁数据没同步过来。第二个坑锁的粒度没有控制好把无关操作也锁住了。有一种常见错误是把锁加的过于粗粒度比如直接用“库存锁定”这种key导致所有商品的扣减操作都串行执行QPS直线下降。正确做法一定是把锁的粒度细化到业务最小单元比如上文例子里的lock:stock:{skuId}每个商品一把锁不同商品的并发扣减互不影响。这一点对于性能的影响极大需要根据业务的资源维度仔细设计锁key的粒度。4. 深入可重入锁原理Redisson是怎么实现重入的4.1 为什么需要可重入可重入性通俗讲就是同一个线程在已经持有锁的情况下再次获取同一把锁不会被自己挡住。这个特性在本地锁中很常见synchronized和ReentrantLock都支持。为什么分布式锁也必须支持可重入因为业务方法之间经常存在嵌套调用。比如你有一个placeOrder()方法加了分布式锁它内部调用了deductStock()而deductStock()也加了同一把锁。如果锁不可重入第二次加锁就会失败直接把自己锁死业务永远走不下去。4.2 Redisson可重入锁的存储结构Redisson的可重入锁之所以能实现与其Redis中的数据结构设计密切相关。它用的是Redis的Hash结构而不是普通的String。锁的key对应Hash的keyHash里面的字段是“持有锁的客户端唯一标识”字段的值是一个计数器表示该客户端重入了几次。举个例子客户端A唯一标识形如UUID:threadId第一次获取锁myLockRedis中存储的数据结构是myLock - { UUID:threadId: 1 }如果同一个客户端A再次获取同一把锁Redisson会检测到Hash中已经有这个客户端的记录就把计数加1myLock - { UUID:threadId: 2 }释放锁时每释放一次计数器减1。只有当计数器减到0时Hash结构才会被删除锁才算真正释放。如果中间某次释放操作因为异常被跳过计数器没有归零锁就会一直被这个客户端占据——所以释放锁的finally块必须保证执行到。4.3 核心Lua脚本剖析Redisson实现加锁和释放锁的核心逻辑封装在了Lua脚本中利用Redis单线程执行Lua脚本的原子性来保证操作不可分割。这里我把加锁和释放锁的脚本简化出来对照着看更容易理解。加锁时的核心逻辑如下简化版只保留关键步骤-- 判断锁的Hash是否存在且当前客户端是否已经持有 if (redis.call(exists, KEYS[1]) 0) then -- 锁不存在直接创建Hash并设置过期时间 redis.call(hincrby, KEYS[1], ARGV[1], 1) redis.call(pexpire, KEYS[1], ARGV[2]) return 1 end if (redis.call(hexists, KEYS[1], ARGV[1]) 1) then -- 当前客户端已持有锁重入计数1并重置过期时间 redis.call(hincrby, KEYS[1], ARGV[1], 1) redis.call(pexpire, KEYS[1], ARGV[2]) return 1 end -- 锁被其他客户端持有返回0表示获取失败 return 0这段脚本的逻辑非常清晰先查锁是否存在不存在就创建存在但持有者是自己就加计数存在但持有者不是自己就返回失败。整个判断加设置的过程在一个Lua脚本里完成不会出现“判断完锁不存在还没来得及创建就被另一线程抢走”的竞态问题。释放锁的核心逻辑如下if (redis.call(hexists, KEYS[1], ARGV[1]) 0) then -- 锁不属于当前客户端不能释放 return nil end local counter redis.call(hincrby, KEYS[1], ARGV[1], -1) if (counter 0) then -- 计数大于0说明重入还没完全退出重置过期时间后返回 redis.call(pexpire, KEYS[1], ARGV[2]) return 0 else -- 计数为0删除锁 redis.call(del, KEYS[1]) return 1 end你瞧释放锁的时候先检查持有者身份再减少计数计数为零才真正删除锁。整个流程同样是原子的。所以“误删别人的锁”这种问题Redisson在脚本层面就挡住了。4.4 加锁等待队列与非公平锁Redisson的tryLock支持等待时间等待的本质是客户端在锁获取失败后进入一个发布订阅的等待机制。当一个线程释放锁时Redisson会向特定的Channel发布一条释放锁的消息。正在等待这把锁的其他客户端会订阅这个Channel收到消息后重新尝试抢锁。这里相当于使用了一个简单的信号量通信机制来减少无效抢锁的轮询次数。Redisson提供的默认锁是非公平锁即等待时间最长的线程不一定优先获得锁而是所有等待线程一起竞争。如果你需要公平锁Redisson也提供了FairLock底层实现会更复杂一些会维护一个等待队列按请求顺序分配锁。但在高并发业务中非公平锁的吞吐量通常更高我在实际项目中绝大多数场景都用的是默认的非公平锁。5. 读锁与写锁处理读写场景的利器5.1 为什么需要读写锁前面讲的锁都是互斥锁同一时间只能有一个线程获取。但在真实的业务场景中“读操作”和“写操作”对资源访问的要求是不对等的。多个线程同时读一个资源是安全的但如果一个线程在写其他线程还去读就可能读到中间状态的数据。如果在纯读场景下也使用互斥锁会导致大量无意义的线程等待白白浪费并发能力。Redis中有一个现成的解决方案RedissonReadWriteLock也就是Redisson实现的读写锁读锁与读锁共享读锁与写锁互斥写锁与写锁互斥。5.2 Redisson读写锁的规则与实现方式读写锁的使用方式和JDK的ReentrantReadWriteLock几乎一样获取读锁和写锁只需要分别调用对应方法RReadWriteLock readWriteLock redissonClient.getReadWriteLock(anyLock); RLock readLock readWriteLock.readLock(); RLock writeLock readWriteLock.writeLock();当持有读锁时其他线程可以继续获取读锁大家共享并发读取但如果有线程尝试获取写锁会被阻塞直到所有读锁释放。当持有写锁时读锁和写锁都会被阻塞相当于独占资源。这套规则特别适合缓存类业务多个请求并发读缓存非常安全一旦需要回源数据库重建缓存就获取写锁独占总线。在底层的存储结构上RedissonReadWriteLock同样使用Hash结构存储。它会在Redis中维护两个不同的Key分别用于读锁和写锁的计数并通过一组Lua脚本控制读锁与写锁之间的互斥关系。当一个线程持有写锁时脚本通过标记位感知当前是写状态从而拒绝其他线程的读锁申请。5.3 读写锁典型应用缓存重建我有个运营系统每天凌晨要刷新一批热门商品数据到Redis缓存中。刷新是一个典型的重型写操作可能在某个瞬间让缓存中的数据不完整。之前的方案是简单粗暴地清空缓存再重建但这个过程中如果用户请求来读就会瞬间打到数据库上有击穿风险。用Redisson的读写锁后流程变成刷新任务获取写锁然后重新加载数据期间用户获取读锁时会阻塞等待数据加载完毕写锁释放用户拿到读锁后立即从缓存读取完整数据。由于多个读锁之间是共享的正常忙时大量用户并发读取缓存完全不受影响只有重建缓存的那几秒存在短暂等待整体体验非常平滑。这段业务的核心实现参考以下骨架public Object getProductDetail(Long productId) { String lockKey lock:product:detail: productId; RReadWriteLock rwLock redissonClient.getReadWriteLock(lockKey); RLock readLock rwLock.readLock(); Object cached cacheService.get(productId); if (cached ! null) { return cached; } // 尝试获取写锁去重建缓存 RLock writeLock rwLock.writeLock(); writeLock.lock(10, TimeUnit.SECONDS); try { // 双重检查防止其他线程已经重建完成 cached cacheService.get(productId); if (cached null) { cached productService.loadFromDb(productId); cacheService.put(productId, cached); } } finally { writeLock.unlock(); } return cached; }注意看代码逻辑这里读操作直接读缓存只有缓存未命中时才需要尝试获取写锁回源数据库。同时做了二次检查相当于CAS中的Check-Then-Act避免多个线程同时发现缓存未命中然后并发重建。这种“读多写少”的场景读写锁的并发收益远高于互斥锁。5.4 使用读写锁的一个容易踩的坑读写锁虽然灵活但使用不当也会引入新的问题。最常见的是“锁升级”问题一个线程持有读锁的同时再去尝试获取写锁会发生死锁。因为写锁必须等待所有读锁释放而当前线程自己还握着读锁它永远在等自己释放锁。JDK的ReentrantReadWriteLock明确规定“读锁不可升级为写锁”Redisson同样是这个规则。你只能在代码结构上规避先用读锁判断判断需要更新数据时先释放读锁再获取写锁。另外需要大幅提高警惕的是如果读操作里误加了一个耗时的远程调用大量线程同时持有读锁阻塞在那里一旦某个线程想升级为写锁整个系统的锁等待时长会被无限拉长最终可能拖垮Redis连接池。读写锁适用的场景一定是“读多写快”如果写操作本身也特别耗时建议慎重评估是否值得引入读写锁硬上不如直接用互斥锁稳妥。6. Redisson分布式锁的典型故障与排查心得6.1 常见故障现象速查表我把实际项目中遇到过的几种典型故障场景整理成了一个表格方便你定位问题时直接对照故障现象可能原因排查思路tryLock一直返回falseRedis连接不可用锁key被其他业务长期持有检查Redis连接和慢查询查看锁的TTL是否异常锁提前释放导致并发问题看门狗续期未生效手动指定leaseTime过短排查是否显式设置了leaseTime确认Redisson版本是否正确启用看门狗unlock时抛IllegalMonitorStateException当前线程不持有锁可能在锁过期后未重新获取就释放在finally中增加isHeldByCurrentThread判断高并发下大量线程堆积waitTime设置太长业务执行太快但锁等待时间不合理调低waitTime采用快速失败策略频繁FullGC导致锁失效Redisson底层使用的Netty线程池被阻塞检查堆内存和GC日志考虑使用异步锁或调节线程池参数6.2 一个真实事故锁过期引发的超卖问题我之前负责过一个商城项目初期库存扣减用的是Redisson的tryLock线上一直很平稳。某天大促当天突然出现库存扣成负数的情况。我当时第一反应是锁失效了排查后确认了具体原因业务团队在代码里显式给tryLock传入了leaseTime设置了10秒过期但那个扣库存的接口外呼了一个第三方风控服务极端情况耗时超过了10秒。锁到期自动释放另一个请求获取锁成功两个请求同时扣减同一份库存超卖就出现了。定位到问题后修复方案其实很简单改为不显式传leaseTime让Redisson启用默认的30秒租约和看门狗自动续期。这样即使外部服务响应慢只要线程还活着锁就会一直续期不释放线程一旦宕机30秒后锁自动过期也不会造成永久阻塞。这个事故给了我一个印象非常深刻的教训**分布式锁的租约时间设置不是拍脑袋决定的必须结合业务的最大执行时间考虑。**如果业务执行时间本身就存在峰值波动比如调外部接口、等待IO响应那么明确使用看门狗的自动续期机制通常比手工设置固定过期时间可靠得多。6.3 锁的监控与治理建议分布式锁的使用量一旦上来不能只靠出问题时再排查日常监控也很重要。我们团队目前的做法是为每个业务锁的key加上统一的监控埋点记录获取锁的耗时、等待时间、成功率和释放时长的分布。这些指标配合Redis的慢日志足以在故障苗头出现时及时定位原因。另外锁的key命名规范也值得在团队内统一。建议采用业务域:资源类型:资源ID的三段式命名比如trade:order:12345这样排查问题时能快速确认是哪条业务链路的锁也能避免因为不同业务之间共用key导致的逻辑串扰。锁的key一定要设置合理的TTL兜底看门狗机制虽然默认开启但如果你在处理非常特殊的场景比如使用了lock()方法且没有释放锁的代码路径兜底TTL就是最后一道保险。7. 从Redisson源码看分布式锁的可靠边界7.1 RedissonLock的自动续期实现细节要真正理解Redisson光看文档不如看源码来得透彻。RedissonLock类是核心。当你调用tryLock并未显式指定leaseTime时Redisson会走一个特殊的分支代码里可以看到它启动了一个scheduleExpirationRenewal的方法。这个方法创建了一个定时任务每过internalLockLeaseTime / 3就会执行一次续期操作。默认internalLockLeaseTime是30秒因此每10秒执行一次把锁的过期时间重置为30秒。同时这个续期调度任务会在锁被释放或者客户端进程停止时主动取消不会无限期续期下去。这里的续期操作本身也是通过一段Lua脚本完成的脚本将锁的过期时间通过pexpire重置。由于执行脚本的Redis是单线程的并发续期操作也是安全的。这个机制在源码里的体现非常精妙定时任务的取消通过ExpirationEntry对象来做引用计数确保只有完全没有锁持有者时才终止续期。7.2 RedLock与真正的“绝对安全”如果你对分布式锁的可靠性有更极致的追求可能会想到RedLock算法。RedLock的思想是在多个独立的Redis节点上同时加锁只有超过半数节点成功才算加锁成功。RedissonClient中确实提供了RedissonRedLock和RedissonMultiLock等实现。但我个人的看法是在绝大多数业务场景下单节点Redis配合Redisson的看门狗机制已经足够尤其是在数据一致性要求可以通过事务、唯一索引等其他手段兜底时RedLock的引入会让系统复杂度和运维成本大幅上升像Clock Drift这样的边界问题本质上也无法100%消除。真正需要严格保证一致性的场景业界更倾向于直接用强一致的服务如etcd或ZooKeeper来承载锁。所以我通常建议团队分布式锁只当作保证“概率上的互斥”使用核心数据的一致性还需要通过数据库层面来兜底不要把所有安全性都押在一把Redis锁上。7.3 高并发场景下的锁性能优化思路几个优化经验放在最后都是实战提炼出来的锁的粒度精细化把大锁拆成小锁能大幅提升并发度。典型做法就是分片锁每个key管一段数据。减少持锁时间加锁之后做最必要的操作就释放像外部IO、耗时计算尽量挪到锁外面。看门狗可以保证锁不提前失效但不代表你可以放心在锁里跑十分钟持锁时间过长对系统整体吞吐量的拖累是实实在在的。使用异步锁Redisson提供了lockAsync、tryLockAsync方法结合CompletableFuture可以显著减少线程阻塞在SpringWebFlux这类响应式场景里几乎是必备技能。本地缓存与分布式锁结合使用高频读取场景先查本地Caffeine缓存未命中再上锁回源Redis和数据库。这能把对Redis锁的调用频率降低一个量级系统稳定性自然就上去了。在实际操作中我发现很多线上分布式锁引发的事故并非Redisson本身的问题而是使用者对它内部的运行机制缺乏足够的理解和敬畏比如不看门狗的续期策略、不清楚锁升级死锁的边界条件、不考虑锁粒度对QPS的影响等。把原理层面梳理清楚再回归到代码实践你会发现Redisson分布式锁用起来是很省心的它在API设计上已经帮你屏蔽了大量复杂的分布式系统难题。如果你准备在项目中正式使用Redisson做分布式锁我的建议是先从库存类场景入手把读写锁和可重入锁各跑一遍理解每一步操作对应Redis中的哪一次写入再逐步扩大到定时任务调度、防抖幂等这类场景踩坑的概率会小得多。