☰
Redis分布式锁演进:从SETNX到Redisson看门狗
2026/10/15 23:08:53 网站建设 项目流程

1. 问题背景:为什么分布式环境下还需要一把“锁”

先说一个最常见的场景。某个团队维护了一套订单服务,早期是单体架构,并发不高的时候直接用本地锁(synchronized 或者 ReentrantLock)就能保证同一个订单不会被两个线程同时处理。后来服务拆成多实例部署,一台变成三台,问题立刻来了:本地锁只锁得住当前进程,请求被负载均衡分散到三台机器上,同样的“判断用户余额并扣款”逻辑完全可能被 A 实例和 B 实例同时执行。于是大家开始寻找一个“所有实例都能看到的锁”,Redis 因为本身已经是基础设施,几乎成了第一选择。

这个项目标题里提到的“Redis 分布式锁”,本质上就是利用 Redis 的原子操作实现跨进程互斥。听起来很简单,但实际落地时牵扯的问题非常多:锁怎么加、锁怎么释放、锁超时了怎么办、业务执行时间比锁过期时间还长怎么办、主从切换会不会导致锁丢失……每一个问题背后都是一次生产事故的教训。

我之所以写这篇复盘,是因为最近又帮一个朋友排查了他们线上偶发的“重复支付”问题,最后定位到竞态窗口就出在锁的过期时间设置上。这个过程中梳理了一遍从 SETNX 一路演进到 Redisson 看门狗的完整链路,发现很多细节如果不抠清楚,确实容易踩坑。

不管你是刚接触分布式系统的新手,还是已经在生产环境里用过 Redis 锁但没完全吃透原理的人,这篇文章的目标都是让你看完之后能看清:每个方案背后的权衡是什么,为什么最终社区会收敛到 Redisson 这种“看门狗”思路,以及你在自己的项目里到底应该怎么选。

2. SETNX 时代:一把最原始的锁

2.1 最初的最初:SETNX + EXPIRE

Redis 从 2.6.12 版本开始支持在 SET 命令上直接附加 NX 和 EX 参数,在这之前大家用的是 SETNX 命令。SETNX 的含义是“SET if Not eXists”,也就是只有当 key 不存在的时候才能设置成功。这个语义天然适合做锁:

# 尝试获取锁 SET lock_order_1001 my_token NX EX 30

这条命令的意思是:只有当lock_order_1001这个 key 不存在时,才设置它,并且给它设置 30 秒的过期时间。如果返回 OK,说明当前实例拿到了锁;如果返回空,说明锁被别人持有。

为什么需要 EXPIRE?因为分布式环境下,持有锁的实例随时可能崩溃,如果没有过期时间,这把锁就永远没人释放,其他实例只能干等——这是分布式系统中的“死锁”问题。

我见过很多刚开始接触分布式锁的开发者,第一版代码就是直接调 SETNX,然后忘记设置过期时间。开发环境下没问题,因为测试人员不会去吧 Redis 里的 key 删掉;但生产环境一旦有节点 crash,锁就永远卡在那里,只能靠人工清 key 恢复。这种事故的修复流程非常被动:你需要先监控到大量请求超时,然后排查所有 Redis key,最后找到那个没有 TTL 的锁 key 手动删除。整个过程耗时几十分钟,而这几十分钟内业务是完全不可用的。

2.2 误删锁:最经典的坑

SETNX + EXPIRE 解决了“死锁”问题,但紧接着就会遇到第二个坑:业务执行时间超过锁过期时间,锁自动释放了,另一个线程 B 获取到锁开始执行。此时线程 A 执行完毕,想要释放锁。如果释放逻辑简单粗暴地执行 DEL key,就会把线程 B 持有的锁删掉。线程 C 紧接着又获取到锁……于是多个线程实际上同时进入了临界区。

这个问题的本质是:释放锁的操作必须验证“这把锁是不是我的”。

解决方案是给锁的 value 设置一个唯一标识:

# 获取锁,value 是当前请求的唯一标识 SET lock_order_1001 uuid_xxxx NX EX 30 # 释放锁,需要先比较 value 再决定是否删除

比较和删除两步操作放在客户端逻辑里做,会有竞态窗口。为了保证原子性,需要用 Lua 脚本来实现“判断 + 删除”:

if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end

这段 Lua 脚本的意思是:先把锁的 value 取出来,如果等于当前请求的唯一标识ARGV[1],才执行删除;否则什么都不做。因为 Redis 是单线程执行 Lua 脚本的,所以这个“先判断再删除”的流程在 Redis 端是原子的,不会被打断。

踩过这个坑之后,团队一般会给锁的 value 用 UUID 或者“设备 IP + 线程 ID + 自增序列”这种全局唯一的字符串,确保任何两个请求的锁标识都不可能相同。

2.3 SETNX 三件套的正确姿势

把 GET + DEL 换成 Lua 脚本,把 SETNX + EXPIRE 换成 SET NX EX 原子操作,基础版分布式锁的完整链路就能闭环了:

  1. 加锁:SET key uuid NX EX ttl
  2. 业务逻辑:执行临界区代码
  3. 释放锁:执行 Lua 脚本,校验 UUID 后删除

这套方案能覆盖 90% 以上的基础场景,代码量少,不依赖额外组件,轻量服务完全可以用这个方案。我自己的经验是:如果团队对 Redis 的使用已经比较熟练、业务对锁的需求只有“互斥 + 防死锁”这两点,其实没必要一上来就套 Redisson,徒增依赖和配置成本。

但要是想得更远一点,这套方案的隐患也很明显:

  • 锁的有效期是固定的。业务执行时间不可控时,锁可能提前释放,导致并发安全问题。
  • 如果锁过期时间设置太长,一旦持有锁的实例真挂了,其他实例需要等待很久才能抢到锁,影响可用性。
  • 不具备公平性。多个实例同时争抢,谁先到达 Redis 谁就赢,无法实现“先到先得”的排队效果。
  • 不可重入。同一个线程如果递归调用加锁逻辑,第二次会锁住自己。

其中第三个问题其实就是“锁标准之争”的起点——当业务对公平性有要求时,SETNX 方案基本无能为力。

3. Lua 脚本:从朴素方案到原子性保障

3.1 为什么必须靠 Lua 而不是客户端组合命令

很多开发者会有疑问:判断 value 和删除 key,不就是先 GET 再 DEL 吗?放客户端里写也没什么问题吧?

实际上问题不小。分布式场景下,Redis 的每个命令都是单独执行的,两个命令之间完全可以插入其他客户端的行为。比如线程 A 执行 GET 拿到了自己的 UUID,然后线程 B 在 A 删除之前就把锁抢过去了——这个概率虽然不高,但一旦发生就是线上事故。

要做到“判断”和“删除”之间没有任何缝隙,唯一可靠的方式是让这两个操作在 Redis 服务端原子完成。Redis 的 Lua 脚本天然满足这个需求,因为它会在执行期间阻塞其他命令,保证了脚本内部的逻辑不会被外部干预。

同样的原理也适用于“加锁 + 过期时间”,客户端里分别执行 SET NX 和 EXPIRE 也有竞态窗口:SET NX 成功之后、EXPIRE 执行之前,如果进程崩溃,锁同样会永久存在。所以标准做法就是用 SET NX EX 一条命令完成设置和过期时间绑定。

3.2 Lua 脚本的完整锁实现

一段稳妥的加锁脚本长这样:

-- KEYS[1] 是锁的 key -- ARGV[1] 是当前请求的唯一标识 -- ARGV[2] 是锁的过期时间,单位毫秒 if redis.call("set", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) then return 1 else return 0 end

很多人习惯用 EX 指定秒级过期时间。我的建议是如果业务对时间敏感,优先考虑 PX 毫秒续期,因为有些框架的看门狗续期是基于毫秒计算的,单位不一致容易导致边界判断失误。

释放锁则用之前提到的那段脚本。把这两段脚本提前用SCRIPT LOAD加载到 Redis,拿到 SHA 摘要后每次调用EVALSHA,可以减少网络传输的开销。实测下来的效果是,在高 QPS 场景下,每次锁操作的耗时能从 Lua 脚本完整传输的约 0.2ms 降低到 0.1ms 以内,对于追求极致性能的服务确实是个值得做的优化。

3.3 Lua 方案还没解决的难题

脚本化之后,原子性问题是解决了,但分布式锁最痛苦的“业务超时”问题依然没有绕过。过期时间设置的逻辑永远是矛盾的:设置短了,业务一慢锁就没了;设置长了,真崩溃时要等很久才能恢复。

打个比方,锁的过期时间就像餐厅排队时取的号。你预估自己 20 分钟能排到座位,但前面的人突然聊起来了,吃了一个小时。服务员按规矩把你的号作废,下一批顾客进场。此时你正啃着鸡腿,突然发现你的号被吊销了,新顾客已经坐在你对面——两个顾客在同一张桌子上吃饭。

分布式锁也一样:锁过期不是锁的主人主动释放,而是 Redis 按时间单方面判定。只要持有锁的线程还在运行中,它的语义就应该是“锁还在有效期内”,而不是“物理时间到了就作废”。Lua 方案解决不了这个问题,因为它不能感知持有者的状态。

正是这个痛点,催生了“看门狗”这种自动续期机制。

4. Redisson 看门狗:给锁加上自动续期

4.1 看门狗原理:锁不再“过期作废”

Redisson 的思路很直白:既然锁过期时间不好设,那就让获得锁的线程在运行期间定期“续约”——只要业务还在跑,看门狗就持续给锁延期,直到业务结束主动释放锁。

Redisson 默认的锁超时时间是 30 秒,看门狗每隔 10 秒会检查一次:如果锁还在(说明线程还没释放),就把过期时间重新设置为 30 秒。这个“续期操作”同样是基于 Lua 脚本完成的原子操作,思路和前面的统一释放脚本类似。

具体流程可以简单描述为:

  1. 客户端请求加锁,Redisson 底层执行 Lua 脚本,在 Redis 中创建一个 hash 结构,记录锁名称、持有者标识和加锁次数。
  2. 加锁成功后,开启一个后台定时任务(调度线程池)作为看门狗。
  3. 看门狗每 10 秒执行一次续期操作:把锁的过期时间重新刷新为 30 秒。
  4. 业务逻辑执行完毕,调用 unlock 方法,Redisson 执行释放脚本。
  5. 如果业务线程长时间不结束(比如因为死循环或 IO 阻塞),看门狗也会一直续期,直到线程停止或被外部终止。

这个机制带来的最大改变是:锁的有效期从“固定时间”变成了“动态时间”——只要持有者还活着,锁就永远不会过期。这就解决了前面 SETNX 方案里“业务还没执行完锁就没了”的核心痛点。

4.2 源码视角:看门狗是怎么实现的

虽然不要求每个人都读一遍 Redisson 源码,但理解几个关键类的职责对排查问题很有帮助。

在 Redisson 中,加锁的核心逻辑在RedissonLock类里。lock()方法最终会调用tryAcquire,内部先尝试用 Lua 脚本加锁,如果加锁成功且没有指定leaseTime(锁的过期时间),则启动一个scheduleExpirationRenewal任务。

这个任务的核心逻辑是:

  • 每 10 秒执行一次renewExpiration。
  • renewExpiration内部执行一段 Lua 脚本:如果锁还存在且持有者是自己,就把 expiretime 重置为 30 秒。
  • 如果执行续期时发现锁已不存在或者持有者变了,就取消调度任务。

这里有一个非常关键的细节:看门狗续期的时候一定会检查“持有者是不是自己”。如果锁已经被别人抢走(比如网络分区导致锁超时丢失),看门狗不会盲目续期覆盖别人的锁。

Redisson 还提供了tryLock(long waitTime, long leaseTime, TimeUnit unit)这个方法。当leaseTime大于 0 时,锁会使用固定过期时间,看门狗机制不会启用;当leaseTime设置为 -1 时,才会启用看门狗自动续期。这个细节特别容易踩坑:很多开发者以为传了 leaseTime 就能自动续期,实际上恰恰相反——传了固定时间反而不续期。

提示:如果业务场景对锁的最长持有时间有硬性限制(比如防止死循环情况下锁无限期被持有),一定要设置 leaseTime 作为兜底。看门狗无限续期是一把双刃剑,它解决了“业务没跑完锁没了”的问题,但也可能让一个异常线程永远占用锁。

4.3 看门狗机制的价值边界

看门狗解决的是 SETNX 方案中“固定过期时间”带来的核心矛盾,但它并不是银弹。它的价值边界在于:

  • 适用于“业务执行时长不可控但必须保证互斥”的场景,比如分布式任务调度、库存扣减等。
  • 适用于“持有锁的节点不会无缘无故地长时间暂停”的假设。如果持有锁的节点发生 STW(比如 Full GC),看门狗线程本身也可能停顿,导致续期失败,锁依然会被释放。

这个问题在 Redisson 的实际使用中确实存在。当 JVM 发生长时间的 Full GC 时,业务线程停止了,但看门狗线程也可能被 GC 卡住,没有成功续期,锁就释放了。其他节点拿到锁进入临界区,原节点的业务线程 GC 结束后也继续执行,就产生了并发竞争。

不过话说回来,这个问题属于极端场景。生产环境里 Java 应用配置了 G1 回收器后,Full GC 时间一般能控制在几十毫秒级别,远小于 10 秒的看门狗续期窗口。真正危险的场景是异地多次 Full GC 累积超过数秒,这种情况本身就需要优先解决 GC 问题,而不是期望锁机制来兜底。

5. 架构权衡:三个方案如何选

5.1 核心维度对比

聊了这么多,说到底最后还是要落地到一个问题上:我的项目到底该用哪个方案。

我整理了一张表,把三个方案几个关键维度拉出来对比,方便你根据项目情况快速判断。

维度SETNX + LuaRedisson(看门狗开启)
实现成本低,需自己写脚本低,引入依赖即用
可重入性不支持,需额外实现计数器支持,内部用 hash 记录重入次数
自动续期不支持支持,默认 30 秒窗口每 10 秒续期
公平性不支持默认不支持,可用 FairLock 实现公平
防误删需自行实现 Lua 校验释放内置校验,比较持有者标识
配置复杂度低,只需 Redis 连接中,需引入完整客户端框架
依赖体积轻量,适合简单服务较重,引入大量类库
崩溃兜底依赖 TTL依赖 TTL + 看门狗双重机制

看完这张表你会发现,如果只想要一个“最简单、能跑、没有额外依赖”的锁,SETNX + Lua 完全能满足。如果项目里已经用了 Redisson 作为 Redis 客户端,或者需要重入、自动续期、公平队列这些高级特性,直接用 Redisson 更省心。

5.2 公平性诉求:什么时候需要 FairLock

Redisson 提供了一种特殊的FairLock,它的内部实现是在 Redis 中维护一个有序集合,记录每个等待获取锁的线程的请求顺序,然后按照顺序依次授予锁。

这个需求多出现在任务调度类场景。比如多个实例同时在跑定时任务,业务要求“先发起请求的任务先执行”,如果两个任务同时抢锁,用非公平锁可能造成后面的先执行、前面的后执行,产生不必要的冲突。

但公平锁在实现上会增加复杂度和性能开销,因为每次加锁和释放都要维护队列,高并发下的争抢性能会打个折扣。我的经验是:90% 的业务场景其实不需要公平锁,只需要保证“同一时刻只有一个执行者”就够了。公平与否反而没那么重要。如果业务真的需要严格排队,先和产品确认一下需求是否合理,很多时候只是“觉得应该公平”,但从系统整体来看,无序执行并不会带来实质问题。

5.3 主从架构:锁丢失的隐忧

还有一个分布式锁绕不过去的问题:Redis 主从切换。所有客户端写主节点,从节点异步同步数据。当主节点崩溃时,从节点提升为主节点,此时尚未同步到从节点的锁 key 就会丢失,新的主节点上没有这把锁,后续请求就能直接加锁成功。

针对这种情况,Redis 官方提出的 RedLock 算法(红锁)要求写入多个独立的 Redis 实例,只有大多数实例加锁成功才算抢锁成功。复杂度很高,而且社区对 RedLock 的有效性一直有争议,很多高水平的架构师也公开表达过多实例红锁在极端情况下并不严格的看法。

我的观点是:如果你的系统可以容忍极少情况下锁的短暂失效(比如秒杀场景,多卖一件商品可以接受通过额外校验兜底),那么单实例 Redis + 看门狗方案足够用;如果你的系统对一致性要求极其严苛(比如金融支付类),应该优先考虑基于持久化存储(数据库行锁、ZooKeeper 临时顺序节点)的方案,而不是继续在 Redis 上折腾。

6. 真正生产中的常见问题与排查实录

6.1 误删锁:你的锁被别人释放了吗

这是最经典的线上问题。症状是某个接口偶尔报“并发冲突”,排查发现多个请求同时进入了临界区,但看代码逻辑里明明是加了锁的。

排查步骤:

  1. 先确认加锁和释放锁是不是同一把 key。
  2. 再确认释放时是否校验了持有者标识。
  3. 最后看释放逻辑是不是用了 Lua 脚本保证原子性。

这三个环节,任何一个做错了,都可能导致误删。我有一个同事就踩过这个坑:他是用 JEDIS 客户端先 GET 再 DEL,没有用 Lua 脚本,结果在低峰期没出问题,一到高峰期流量上来,误删概率陡增。

复现方式很简单:开两个服务实例,手动把第一个实例加锁后休眠一段时间,然后模拟第二个实例尝试加锁并释放锁,看看锁的状态。如果第二个实例的释放操作直接把 key 删了,说明校验逻辑没生效,不是误判。

6.2 看门狗没续期:锁为什么还是会丢

排查了几次线上问题之后,我发现一个共性:看门狗续期不是 100% 生效的。它的续期由调度线程池执行,如果应用线程池被打满,或者 GC 停顿超过 10 秒,续期操作就会延迟甚至失败。

还有一个容易忽视的坑:在应用中使用 Redisson 时,如果配置了两个不同的 Redisson 客户端连接同一个 Redis,但其中一个客户端配置了固定的lockWatchdogTimeout,另一个没配,那么两个客户端创建锁的续期行为可能不一致。

我遇到过一个场景:业务 A 用 Redisson 的tryLock(0, 10, TimeUnit.SECONDS)设置了固定过期时间,业务 B 用默认看门狗 30 秒。两个业务共用同一个 Redis key 前缀,结果 A 的锁到期后 B 立刻就能抢到,导致 A 和 B 实际上同时执行。这个问题定位了很久才发现是两个服务的锁过期时间配置不一致导致的。

排查思路:查看 Redis 中锁 key 的 TTL 变化。如果 TTL 恒定为某个固定值不减,说明没有启用看门狗(因为看门狗会把 TTL 刷新回 30 秒)。如果 TTL 持续减小到 0,说明看门狗没工作,需要检查是否设置了 leaseTime。

6.3 全局锁引发的性能瓶颈

分布式锁最容易被忽略的问题是:它会把并发量直接压到 Redis 单节点上。假设你们每秒有 1 万请求都在抢同一把锁,Redis 的单线程模型会瞬间成为瓶颈,因为每次加锁都是串行执行。

针对高并发下的锁竞争,有几个优化方向:

  • 锁粒度拆分。对同一个订单 ID 加锁,天然分散,但如果是全局库存锁,可以把库存分成多段,每段对应一把锁。
  • 缩短临界区。锁里面只放必须互斥的代码,其他操作移到锁外面,减少锁持有时间。
  • 引入预扣减。在 Redis 中先减库存,异步扣减数据库,锁只保证数据库扣减的一致性。

我曾经参与过一个库存服务的优化:原来整个“减库存 + 更新销量 + 写审计日志”都在锁里执行,平均耗时 80ms。优化后把写审计日志移出锁外,锁内操作降到 20ms,同样的锁粒度下吞吐提升了接近 4 倍。

6.4 实战排查工具与命令

如果你需要快速确认某个 Redis key 是不是锁,可以用以下命令直接观察:

  • TTL lock_key:查看剩余过期时间,如果持续在 20~30 秒之间波动,说明看门狗正在续期。
  • GET lock_key:查看 value,确认持有者标识能对应到哪台机器的哪个线程。
  • TYPE lock_key:Redisson 的锁是 hash 类型,而 SETNX 方案是 string 类型,从类型能判断是用什么客户端加的锁。
  • MONITOR:在高危操作前开启 MONITOR 监听 Redis 命令流,看有没有可疑的 DEL 操作抢在释放前执行。

另外推荐在客户端代码里给锁的关键事件打上日志:加锁成功、加锁失败、开始续期、续期失败、释放锁成功。尤其是续期失败的日志,很多时候是分钟级偶发一段时间,不记录日志根本查不出来。

7. 工程实践:构建一个稳健的分布式锁

7.1 落到代码层:什么时候用什么方案

如果你用的是 Spring Boot + Redis 的轻量项目,我的建议是分情况选择:

  • 单体多实例部署、锁需求简单、不想引入额外依赖:直接用 SETNX+Lua,锁的过期时间设置足够大(比如业务平均耗时的 10 倍以上),配合监控告警,出问题能及时发现。
  • 已有 Redisson 依赖、需要分布式组件复用:直接用 Redisson 的RLock,并按需开启看门狗。
  • 重入场景较多、锁粒度较细:优先用 Redisson,它内置重入计数,省去自己实现低层 hash 锁结构的麻烦。
  • 对锁的公平性有强诉求:Redisson FairLock 可以满足,但要接受性能损失。

7.2 锁过期时间到底怎么设

这是一个涉及业务判断的问题,我给不出统一的公式,但可以给出经验参考:

  • 如果业务是同步调用,且对方接口响应时间有保证(比如缓存查询),锁过期时间可以控制在 1~2 秒。
  • 如果业务包含外部接口调用、数据库交互、网络IO,甚至可能触发重试,锁过期时间建议 10~30 秒。
  • 长事务场景(比如异步任务拆分、人工审批流程)不建议用 Redis 锁,建议用数据库乐观锁、状态机或者 ZooKeeper 锁。

在实际项目中,我倾向的做法是:设置一个合理的默认值(10 秒),同时配合看门狗自动续期,这样既不会因为默认值太短导致业务超时,也不会因为太长导致故障恢复慢。

7.3 加锁失败后的等待策略

SETNX 方案的一个不足是国内很多代码写得像这样:

Boolean locked = redisTemplate.opsForValue().setIfAbsent(key, value, timeout); if (locked) { // 执行 } else { return "系统繁忙"; }

拿到锁失败就直接返回“系统繁忙”,这在瞬时高并发下会造成大量请求被拒,体验很差。正常做法是加一个自旋重试:失败后 Sleep 一小段时间(比如 50ms)再重试,最多重试 N 次,每次间隔随机化,避免所有请求同时冲过来。

Redisson 的tryLock(waitTime, leaseTime, TimeUnit)方法天然支持“等待锁”模式:在 waitTime 内阻塞等待其他线程释放锁;如果超时还没拿到,就返回 false。这样能极大减少“直接拒绝”的体验问题。

但要注意:tryLock如果设置 leaseTime 为 -1 启用看门狗,那 waitTime 以内的等待是阻塞的,会占用线程资源。这个在设计线程池大小时要提前考虑,免得等待锁的线程把线程池占满,其他非锁请求反而进不来。

7.4 锁的监控与告警

线上分布式锁如果没有任何监控,出了问题很难快速定位。我建议至少采集这几个指标:

  • 加锁成功率:一段时间内加锁成功次数 / 加锁总次数。
  • 等待锁平均耗时:能反映锁竞争激烈程度。
  • 锁持有平均时长:能反映临界区代码的执行效率。
  • 看门狗续期失败次数:这是锁丢失的直接信号。
  • Redis 命令耗时(P99):锁的延迟直接影响业务接口延迟。

这些指标可以用 Micrometer 或者 Prometheus 客户端接入,直接挂到现有监控体系里。锁的告警阈值需要根据业务实际调整,比如“加锁成功率低于 90%”这类通用阈值不一定合适,最好先跑一周看基线数据,再确定异常判定线。

8. 写在最后:选型不是一道选择题,而是一道权衡题

从 SETNX 到 Lua 脚本到 Redisson 看门狗,这个演进过程本质上是一连串“取舍”的叠加。

SETNX 方案用最小的成本解决了“多实例互斥”这个基础需求,但代价是过期时间不可控、不可重入、不够公平。Lua 脚本解决了原子性,却解决不了“业务比锁活得更久”的尴尬。Redisson 看门狗用自动续期换来了更强的鲁棒性,但同时也增加了复杂度,还给异常线程留下了无限期占用锁的可能。

我自己在实际项目里的体会是:分布式锁是一个“平时用起来简单、出问题时极其棘手”的组件。很多团队一开始用 SETNX,是因为“够用就行”的心态;但一旦业务开始跨模块、跨服务、要求高可用,这套简单的方案就会出现各种边缘问题。而在真正把 Redisson 引入生产之后,也不要以为万事大吉,看门狗本身也有失效场景。

如果你想做一次系统的归档复盘,我建议可以回去翻一下自己生产环境里加锁相关的事故记录,把每个事故对应到这篇文章里的某个维度:是误删?是超时?是主从切换?还是看门狗没续期?把这些案例总结成一份内部的“锁故障手册”,比任何空洞的方案比较文档都有用得多。

最后再分享一个小技巧:任何分布式锁方案上线之前,都建议写一个并发压测用例,模拟两个线程同时执行临界区 10000 次,验证确实没有出现同时进入的情况。这个测试看起来不起眼,但它能提前把我文章里说的误删、超时两个问题在开发环境就暴露出来,而不是等你上生产了再去紧急答辩。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询