news 2026/9/16 1:39:38

高并发分布式锁选型实战:Redis、ZooKeeper对比与踩坑总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
高并发分布式锁选型实战:Redis、ZooKeeper对比与踩坑总结

我们在做高并发系统时,迟早会碰到“分布式锁”这道坎。单机时代一个synchronized就够了,但应用拆成多个实例部署之后,同一个请求可能落到不同机器上,JVM 自带的锁根本管不住跨进程的互斥。这个标题下面藏着的,其实是三件事:锁要解决什么问题、有哪些主流方案、以及线上真正跑起来后哪些细节会坑到你。

这篇文章我把自己这些年做高并发分布式锁的选型思路、实现方案对比、以及踩过的线上问题都整理出来。无论你是刚开始接触分布式锁,还是已经在某个方案上吃过亏想换个思路,这篇内容都能给你一个比较完整的决策框架。我会直接用从业者的口吻讲,不绕弯子,把关键细节摊开说。

1. 高并发分布式锁的本质:到底要解决什么问题

1.1 单机锁失效的那一刻

先看一个最常见的问题场景:你有一个定时任务,每天凌晨要去扫表、跑批、刷新缓存。单机部署的时候,一个synchronized或者ReentrantLock就能保证同一时刻只有一个线程在执行。但系统一上微服务,实例从 1 台变成 3 台,同样的定时任务在三台机器上同时触发,这时候问题就来了——三个进程同时在跑同一个批处理任务,数据被重复处理,缓存被重复刷新,严重一点还会把不正确的数据写进数据库。

这就是分布式锁存在的根本原因:在分布式环境下,让多个进程之间对同一个共享资源的访问做到互斥。它的本质和单机锁一样,只是作用域从“进程内”扩大到了“跨进程”。

还有一类典型场景是秒杀、抢购、库存扣减。用户请求经过负载均衡分发到不同实例,如果不用分布式锁把库存操作串行化,就会出现超卖。这里有个容易混淆的点:很多人会把分布式锁和数据库事务、乐观锁混在一起用,但其实它们解决的是不同层面的问题。事务保证的是数据一致性,乐观锁保证的是更新不丢失,而分布式锁保证的是“同一时刻只能有一个执行者”这个互斥语义。

1.2 一把合格的分布式锁需要满足哪些条件

在对比方案之前,先建立一个评价基准。我在选型时基本会从六个维度去衡量一个分布式锁方案:

互斥性:任意时刻,只能有一个客户端持有锁。这条是分布式锁的底线,做不到就不叫锁。

安全性:锁不能被误删。比如 A 持有锁,结果 B 把 A 的锁删了,那互斥性就瞬间被破坏。这个问题在 Redis 方案里特别常见,后面我会详细讲。

死锁防护:客户端崩溃或者网络分区后,锁必须能被自动释放,不能死等。也就是说锁必须有“过期时间”或者“会话绑定”机制。

容错性:锁服务本身不能是单点。如果实现锁的组件挂了,不能导致所有业务全挂。

性能:获取锁和释放锁的开销要足够低,不能成为系统吞吐量的瓶颈。

可重入性:同一个线程能否重复获取同一把锁。这个要看具体业务需求,不是所有场景都需要。

这六个维度在不同的技术方案上有各自的取舍。不存在“什么都好”的方案,只有“最适合当前场景”的方案。后面的方案对比,其实就是围绕这几个维度展开的。

2. 四种主流实现方案:数据库、Redis、ZooKeeper、etcd

2.1 数据库方案:老派但依然有生存空间

数据库分布式锁是最容易想到的实现方式,因为它不需要额外引入中间件,直接用现有数据库的能力就能做。实现上分两种:悲观锁和乐观锁。

悲观锁的做法是利用数据库的行级锁,典型语句是:

SELECT * FROM t_lock WHERE resource_id = 'order_123' FOR UPDATE;

这条语句执行后,其他事务再要对同一行做FOR UPDATE就会阻塞,直到当前事务提交或回滚。锁的释放依赖事务结束,这天然满足了“崩溃自动释放”的要求——只要事务超时回滚,锁就没了。

乐观锁的做法则是用版本号字段:

UPDATE t_lock SET version = version + 1 WHERE resource_id = 'order_123' AND version = 1;

通过“受影响行数为 1”来判断是否抢锁成功。这种方式没有阻塞,严格来说更像是一种 CAS 操作,而不是传统的互斥锁。

数据库方案的优点非常明显:简单、可靠、无额外组件。但也有几个突出的硬伤。第一是性能瓶颈,数据库的并发处理能力远低于缓存型中间件,在 TPS 上万的高并发场景下很快会成为系统瓶颈。第二是单点风险,虽然数据库本身有主从和集群,但在锁语义下,主从切换时锁状态的一致性很难保证。第三是死锁和阻塞管理,悲观锁长时间持有时,其他请求排队等待,数据库连接池很容易被打满。

我的建议是,数据库方案适合低并发、逻辑简单的内部系统或者管理后台。高并发核心链路,尽量不要碰它。

2.2 Redis 方案:高并发场景的事实标准

Redis 分布式锁是目前互联网行业应用最广泛的方案。核心原因就两个字:。Redis 的纯内存操作,单线程模型,加上简单的命令语义,让它在高并发场景下能保持极高的吞吐和极低的延迟。

最基础的实现是两条命令配合使用:

SET lock_key unique_value NX PX 30000

其中NX表示只有 key 不存在时才设置成功,PX 30000表示 30 秒后自动过期。释放锁的时候,不是简单DEL,而是要先用 Lua 脚本比对 value 是不是自己的,防止删掉别人的锁:

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

这个最简单的版本,在高并发下存在几个问题。一是锁的过期时间难设定——设短了,业务没执行完锁就自动过期了;设长了,客户端崩溃后锁要很久才能被新请求获取。二是主从切换时有锁丢失风险,Redis 主节点刚写入锁还没来得及同步到从节点就挂了,哨兵把从节点提升为主节点,新主节点上没有这把锁,别人就能再次获取同一把锁。

为了解决这些痛点,业界出现了 Redisson 这类客户端库,用“看门狗”机制做自动续期,把锁的有效期动态延长。后面第四章我会专门展开讲实现原理。至于 Redlock 算法,它在理论上追求多节点层面的互斥,但实际工程中争议很大,我自己的实践是不太推荐,后面也会细说。

2.3 ZooKeeper 方案:强一致性的牺牲品?

ZooKeeper 分布式锁基于它的临时顺序节点(EPHEMERAL_SEQUENTIAL)机制。原理是这样的:所有客户端在同一个锁目录下创建临时顺序节点,编号最小的那个节点持有锁;其他客户端监听自己前一个节点的删除事件,当前一个节点被删除时,后一个节点就去检查自己是不是编号最小的,如果是就获得锁。

这里有几个关键技术点值得展开:

临时节点意味着客户端会话结束或者断连之后,节点会被自动删除,锁自动释放,这是天然的死锁防护。

顺序节点保证了公平性,先来先得,不会有“饿死”现象。

监听机制让客户端在锁释放时能够被及时唤醒,不需要轮询。

同一条路径下的节点,创建时由 ZK 保证全局唯一且有序。这套机制的可靠性非常高,因为 ZK 的 Zab 协议保证了集群节点之间的数据强一致性,只要客户端会话活着,锁就不会丢

但 ZK 方案的代价是性能。创建节点、删除节点、客户端监听事件,每一步都有 ZK 集群内部的同步开销,P99 延迟通常比 Redis 方案高一个数量级。另外,ZK 的客户端会话超时有一个 bug 级别的风险点:如果客户端发生长时间 GC 停顿,会话超时,临时节点被删除,锁被释放,但业务线程其实还没执行完。虽然有这个风险,但在对一致性要求极高的场景下,比如分布式事务协调、配置变更、领导选举,ZK 方案依然是值得信赖的选择。

2.4 etcd 方案:云原生时代的后起之秀

etcd 是 Kubernetes 的底层存储,它的分布式锁实现思路和 ZooKeeper 非常相似,都是基于强一致性的分布式协调服务。etcd 提供的锁能力基于三个核心机制:

Lease(租约):获取锁时创建一个租约,租约有过期时间。如果持有者异常退出,不会续租,租约到期后锁自动释放。这和 Redis 的过期时间思路一致。

Revision(修订版本):所有操作都会产生一个全局递增的 revision 号,客户端可以用它做公平排队。

Watch(监听):客户端监听 key 的变化,在锁释放时第一时间感知,减少无效轮询。

etcd 的锁实现和 ZK 类似,要求客户端在持有锁期间持续续租,否则锁会被自动释放。它比 ZK 更云原生、更轻量,如果你的基础设施已经跑在 Kubernetes 上,etcd 通常是最顺理成章的选择。不过它的问题也和 ZK 一样:吞吐量不如 Redis,且需要独立的 etcd 集群来保证可用性。

2.5 方案横向对比速查表

维度数据库RedisZooKeeperetcd
一致性模型取决于数据库隔离级别AP,主从切换有锁丢失风险CP,强一致CP,强一致
性能极高中等中等
死锁防护事务超时过期时间/看门狗临时节点+会话超时租约+续租
锁丢失风险主从切换有风险主从切换有风险
公平性不支持原生不支持,可用方案模拟顺序节点天然公平Revision 自然有序
运维成本无额外成本高,需要维护 ZK 集群中,K8s 生态下较友好
典型场景内部系统、低并发互联网高并发核心链路金融级强一致场景云原生环境

这张表只是帮你快速建立一个整体印象,真正的选型还要结合业务特点,我在第三章给出具体的决策方法。

3. 选型决策框架:复杂场景下怎么选才不后悔

3.1 先看业务对一致性的容忍度

我在很多技术讨论里看到大家一上来就争论 Redis 方案和 ZK 方案谁优谁劣,这种讨论其实很低效。选型的出发点不是技术先进性,而是业务对一致性的容忍度。

先问自己一个问题:如果这把锁“失效”了,两个人同时拿到了锁,最坏的结果是什么?

如果最坏结果是多扣一次库存、多送一张优惠券、产生了资损,那这个场景对一致性要求极高,建议直接在 ZK 或 etcd 里选。

如果最坏结果只是重复推送一条消息、重复执行一次非关键任务,那 Redis 方案的成本优势和性能优势就非常突出。

很多业务的现实是:对一致性的真实要求并没有想象中那么高。做活动、抢红包、秒杀,稍微超卖一点用事后对账可以修复,这时候用强一致方案反而提高了成本、牺牲了性能。

3.2 我自己的选型四步法

这个选型框架我在团队内部推过很多次,效果还可以,分享出来供大家参考:

第一步,评估业务并发量。如果峰值 TPS 不超过一千,数据库方案完全够用,不用过度设计。如果峰值 TPS 到万级别,Redis 方案是首选。只有当单把锁的争抢频率极高,并且需要公平性时,才需要考虑 ZK/etcd。

第二步,评估锁失效的后果。用上面说的“最坏结果分析”方法,给业务场景做一个容忍度分级。容忍度低的直接排除 Redis,容忍度高的可以放心用。

第三步,评估现有的基础设施。如果团队已经运维着 Redis Cluster,使用 Redis 方案几乎零成本。如果基础设施已经全面云原生、K8s 化,etcd 是自然选择。不要为了分布式锁去专门引入一套 ZK,除非确实需要它的强一致能力。

第四步,评估团队的技术熟悉度。分布式锁实现不难,难的是出了问题后的排查。团队对哪个中间件最熟悉,出问题时能最快的定位,这也是一个不可忽视的因素。

3.3 分场景推荐清单

  • 互联网高并发秒杀、库存扣减:首选 Redis + Redisson。性能高,使用简单,看门狗机制能解决大部分过期问题。对极端一致性的担忧,可以通过后续业务补偿去兜底。
  • 分布式任务调度、定时任务防重:首选 Redis,方案同上。任务场景通常可以容忍锁失效后的重复执行,再配合任务表的业务幂等即可。
  • 分布式事务协调、领导选举:首选 ZK 或 etcd。这类场景对一致性零容忍,且本身并发量不高,强一致方案的性能劣势可以忽略。
  • 分布式配置管理、服务注册发现:首选 etcd/ZK。这不是单纯的锁问题,而是协调服务的主场。
  • 内部管理系统、低并发后台:数据库方案。简单可靠,不需要额外组件,出了问题所有人都看得懂。

4. 核心实现细节剖析:好的代码和能跑的代码是两回事

4.1 Redis 锁的原子性到底怎么保证

先回到 Redis 最基础的锁实现。很多初学者会写两条独立的命令来加锁:

SET lock_key value NX EXPIRE lock_key 30000

看起来没问题,但实际上这两条命令不是原子的。如果第一条执行成功、第二条因为网络抖动或者 Redis 崩溃没有执行,这个 key 就成了永不过期的死锁。正确做法是使用 Redis 2.6.12 之后引入的单命令原子写法,把SET和过期时间绑在一起:

SET lock_key unique_value NX PX 30000

释放锁的时候也面临同样的问题。最忌讳的写法是:

if (redis.get("lock_key") == unique_value) { redis.del("lock_key"); }

这是一个经典的先检查后操作的竞态。虽然检查通过了,但下一个瞬间锁过期了,另一个线程 B 抢到了同一把锁并写入了新的 value,然后线程 A 执行DEL,就把 B 的锁误删了。解决方式是用 Lua 脚本把“检查 value 是否一致”和“删除 key”合并成一个原子操作。为什么 Lua 脚本在 Redis 里是原子的?因为 Redis 是单线程执行命令的,Lua 脚本在执行期间不会被其他命令插入,天然具备原子性。

4.2 Redisson 看门狗机制:续期背后的原理

前面提到锁过期时间不好设,设短了业务没跑完锁就自动释放,设长了客户端崩溃后锁要很长时间才能被其他请求获取。Redisson 的解决方案是看门狗机制。

Redisson 获取锁时,默认锁的有效期是 30 秒(lockWatchdogTimeout参数可配置)。但拿到锁之后,它并不是不管了,而是启动一个后台定时任务,每 10 秒(即 30 秒的三分之一)检查一次锁是否还被当前线程持有,如果还在,就通过 Lua 脚本把锁的过期时间重置为 30 秒。

这个机制的关键在于:锁的有效期是动态续期的,只要业务线程还在执行,锁就不会过期。但要注意,看门狗只在当前客户端进程内运行,它默认使用的是 JVM 层面起的守护线程,不依赖 Redis 服务端。如果客户端进程宕机,守护线程随之消失,锁在最长 30 秒后自动过期,不会造成永久死锁。

看门狗这个设计里有几个隐藏的坑:

业务代码必须通过 Redisson 提供的 API 来调用锁,如果自己手写SET NX PX加锁,Redisson 的续期逻辑不生效,锁还是会被固定过期时间杀掉。

看门狗对长时间持有锁的业务无效。如果业务需要持锁超过几十分钟甚至几小时,靠续期维持是不现实的,需要业务侧自己评估是否有必要长期持锁。

超时会导致看门狗失效。如果用了tryLock(long waitTime, TimeUnit unit)这种带等待时间的重载方法,Redisson 会使用传入的超时时间作为锁的固定有效期,不再启动看门狗。

我在项目里常规做法是:如果业务能预估执行时间,就传入合理的超时时间,不使用看门狗;如果业务执行时间不可控,用不带超时参数的lock()方法,让看门狗做动态续期。前提是,业务本身要有兜底机制,即使锁丢了也不会造成灾难性后果。

4.3 ZooKeeper 锁的完整实现逻辑

ZK 锁的核心代码逻辑,我习惯这样写:

  1. 在锁根节点下创建临时顺序节点,比如/locks/order_123/lock_
  2. 获取/locks/order_123下的所有子节点,排序后找到自己创建的节点。
  3. 如果自己的节点是序号最小的,说明获取锁成功。
  4. 如果自己不是最小序号,监听前一个节点的delete事件,一旦前一个节点删除,重新执行判断。

这里有个非常重要的实现细节:监听的是前一个节点,而不是根节点或所有子节点。这样做有两个目的。一是避免“惊群效应”(herd effect),也就是锁释放时所有等待者都被唤醒,然后互相竞争。只监听前一个节点,锁释放时只会唤醒一个等待者,唤醒开销降至最低。二是保证公平性,编号次序就是抢锁次序,不会出现插队。

ZK 锁能防死锁的关键是临时节点。客户端与 ZK 服务端之间的会话是长连接,如果客户端崩溃或网络断开,会话超时后 ZK 会自动删除所有以该会话创建的临时节点,这就实现了“锁随会话死亡而释放”。

但与 Redis 相比,ZK 锁有另一个角度的陷阱:会话超时时间的设置。如果超时时间设得太短,客户端 GC 停顿这类的短暂不可用会被误判为会话失效,锁被提前释放;如果设得太长,客户端真的崩溃后,其他客户端要等待很久才能获取锁。实践中建议结合业务的 GC 停顿监控数据来设置,通常设置在 5 到 10 秒之间,并且要确保应用容器的 GC Pause 时间远小于这个值。

4.4 可重入锁怎么实现

单机环境里的synchronized天然支持可重入,同一个线程可以多次获取同一把锁。但分布式锁默认是不支持可重入的,因为服务端并不知道多次加锁的是不是同一个客户端。

Redis 方案实现可重入有两种思路。一是用 Redisson,它默认基于 Hash 数据结构实现可重入,结构是hash : { threadId : count },每次重入 count 加一,每次释放 count 减一,减到零才删除 key。二是自己实现,在业务里维护一个本地 ThreadLocal 记录当前线程的锁次数,加锁时如果发现已有锁是自己持有的就直接放行。

ZK 方案的可重入实现麻烦一些,因为 ZK 的节点是持久化的路径,没有“持有者计数”的概念。我的做法是在锁被获取时,把客户端标识(比如host:threadId)写入节点数据(data),再次加锁时先检查节点数据是不是自己,是则重入,否则等待。但要小心,ZK 节点数据是有大小限制的,默认 1MB,这个量级对存储线程标识来说绰绰有余。

其实我建议高并发场景优先考虑 Redisson 的可重入实现,成熟稳定,不用重复造轮子。自己实现可重入时,最容易出 bug 的地方是锁计数没有正确清理,导致锁一直无法释放,形成死锁,排查起来相当痛苦。

5. 生产环境高频踩坑与排查实录

5.1 锁过期了,业务却没执行完

这是 Redis 方案上线后最常遇到的线上问题。表现是:业务突然出现并发冲突,明明加了锁,数据还是被重复处理了。排查后大概率是锁过期时间设置得太短,业务执行时间超过了锁的有效期。

我遇到过的一个具体案例:一个库存同步任务,正常执行时间在 2 秒以内,就设置了 10 秒的超时时间。结果某天数据库慢查询,业务执行了 15 秒。第 10 秒锁自动过期,另一个实例马上拿锁执行同一任务,两个任务同时操作同一批库存,出现了超卖。

这类问题的根治思路是在业务层面对“锁被提前释放”做兜底。比如在业务执行前判断锁是否还在,或者用 Redisson 的看门狗做续期。但更关键的是要意识到:分布式锁中间的“分布式”三个字意味着,锁的有效期不可能完全覆盖业务执行时间,你永远要假设锁可能会丢。在这个假设下做幂等设计,才是高并发系统的正确姿态。

5.2 主从切换导致锁丢失

Redis 主从架构下,锁丢失的场景是这样的:客户端 A 在主节点上写入锁 key,主节点的数据还没同步到从节点,主节点挂了。哨兵把从节点提升为主节点,此时新主节点上没有这把锁。客户端 B 尝试获取同一把锁,成功了。这样 A 和 B 同时持有了锁,互斥性被打破。

为什么 Redlock 算法在这种情况下被提出?它的思路是,不依赖单个 Redis 节点,而是向集群中多个独立的 Redis 节点同时写锁,只有过半节点写入成功才算拿到锁。这样即使某个主从切换导致部分节点丢了锁,只要大多数节点上还有锁,整体上仍然能保证互斥。

但这个算法在工程界争议非常大,包括 Redis 作者和很多一线工程师都发表过反对意见。核心原因是:分布式系统里“网络分区”时,Redlock 无法同时保证安全性和可用性。我个人的实践结论是:如果业务对锁丢失零容忍,不应该用 Redis 方案,直接上 ZK 或 etcd;如果业务能容忍极小概率的锁丢失,直接用 Redis,加业务幂等兜底比用 Redlock 划算得多

5.3 锁误删:大家都犯过的低级错误

锁误删问题在前面 4.1 已经提过原理,这里再讲一个真实的教训。有一次我们排查线上问题,发现某个热点商品的库存扣减偶发出现超卖,但概率极低。查了很久,最后定位到是一位同事在释放锁时只做了简单的DEL操作,没有比对持有者标识。

触发路径是这样的:线程 A 拿锁,设置超时 5 秒。A 业务执行了 4.9 秒,锁只剩 0.1 秒。这时候刚好发生了一次 Full GC,A 被暂停了 1 秒。锁在 A 暂停期间过期。线程 B 获取锁,开始执行业务。A GC 结束,继续执行完业务,然后执行DEL,把 B 的锁删了。线程 C 此时也来抢锁,成功拿到锁,于是 B 和 C 同时执行业务。

这是一个非常经典的“GC 暂停导致锁过期 + 误删锁”的组合场景。理解这个问题的关键是:GC 停顿会让线程的“现实时间”和“逻辑时间”脱节。锁的过期时间是基于现实时间计算的,而线程执行进度是基于逻辑时间推进的。

解决方式就是前面反复强调的:释放锁时一定要用 Lua 脚本做“先校验持有者,再删除”的原子操作。

5.4 惊群效应与连接风暴

ZK 方案中如果实现不当,容易出现惊群效应。假设有 100 个客户端在等待同一把锁,如果每个客户端都监听了锁节点的删除事件,锁一释放,100 个客户端同时被唤醒,同时去竞争创建锁节点。这不仅会造成瞬时大量 ZK 请求,增大节点压力,还会导致大量客户端同时抢锁造成不必要的网络开销和 CPU 消耗。

正确做法是只监听前一个节点。如果业务对线程唤醒的实时性要求极高,还可以考虑用 etcd 的Watch机制,它天然支持只观察前一个 revision,实现方式更优雅。另外,我在 ZK 方案里还会加一个保护机制:客户端被唤醒后不立即抢锁,而是随机加一个 50 到 200 毫秒的小抖动,错峰执行,进一步降低瞬时冲击。

5.5 常见问题速查表

异常现象可能原因排查方向解决方案
加了锁还是超卖锁过期/锁丢失/锁误删查看锁大 key 历史、开启慢查询、检查释放锁逻辑用 Lua 释放锁,看门狗续期,业务幂等兜底
获取锁超时网络抖动/Redis 负载高查看 Redis 的 INFO 命令和慢日志增加重试机制,使用连接池,优化 Redis 参数
锁一直无法释放业务线程死循环/看门狗失效dump 线程栈,检查 Redisson 版本配置使用 tryLock 带超时,兜底释放逻辑
锁被提前释放GC 停顿/网络分区查看 GC 日志,查看 Redis 客户端重连日志合理设置超时时间,考虑 ZK/etcd 方案
大量请求同时阻塞惊群效应/不合理的监听策略查看中间件连接数监控改为只监听前驱节点,加随机抖动
ZK 会话频繁过期会话超时时间设置过短查看客户端 GC 日志和网络状况调大会话超时时间,降低 ZK 心跳频率

这张表是我做了多年分布式系统之后沉淀出来的高频问题集合。实际排查时,建议配合监控系统记录锁的获取耗时、持锁时间、释放耗时三个核心指标,这样一旦出现异常,可以直接定位是获取慢、持锁太久,还是释放失败。

6. 一个完整的 Redisson 接入示例

选型部分讲了很多理论,我最后给一个可以直接抄作业的 Redisson 接入示例,这样你可以更快地在项目里落地。

首先引入依赖,Maven 为例:

<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.20.1</version> </dependency>

然后在application.yml中配置连接:

spring: redis: redisson: config: | singleServerConfig: address: "redis://127.0.0.1:6379" connectionMinimumIdleSize: 10 connectionPoolSize: 50

这里要注意的是connectionPoolSize不能设置太小,否则高并发下容易出现获取连接超时。我见过很多项目用默认的 8 个连接,结果一到秒杀高峰期就报Cannot get a connection,最后都是把连接池调大才解决。

核心业务代码:

@Autowired private RedissonClient redissonClient; public void doBiz(String bizKey) { String lockKey = "lock:order:" + bizKey; RLock lock = redissonClient.getLock(lockKey); try { // 尝试获取锁,等待 3 秒,锁自动过期时间由看门狗维护 if (lock.tryLock(3, TimeUnit.SECONDS)) { // 业务逻辑 } else { throw new BizException("系统繁忙,请稍后重试"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BizException("获取锁被中断", e); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }

这里有几个细节值得说明:

lock.isHeldByCurrentThread()非常重要。如果直接调用lock.unlock(),在锁已经过期被其他线程获取的情况下,会抛出IllegalMonitorStateException。有了这个判断,可以避免不必要的异常。

tryLock(3, TimeUnit.SECONDS)的第一个参数是获取锁的等待时间。如果超过 3 秒还没有拿到锁,直接返回 false。这个参数要结合业务的执行时间设置,不要让大量请求在锁上排队太久。

unlock如果放在finally块里,要注意它本身可能是会抛异常的网络操作。实际生产中,释放锁失败会影响后续流程,所以要么捕获处理,要么让异常抛出后由全局异常处理兜底。

如果是多实例部署的环境,要确保每个实例的RedissonClient连接的是同一个 Redis 集群。我曾经遇到过一个低级错误:测试环境只部署了一个实例,连的是 Redis 单节点,一切正常。上线后多实例部署,有人写死了 IP 连到了同一台 Redis,但网络策略只放行了其中一个实例,导致另外几个实例根本拿不到锁。这种问题排查起来很隐蔽,基本只能靠看日志发现连接异常。

最后再说两句大实话

分布式锁看着简单,用起来处处是坑。我最大的体会是:不要在方案上过度纠结,要在业务兜底上多花心思。任何分布式锁方案都不能保证 100% 的互斥性,与其追求绝对的强一致,不如假设锁一定会丢,把后续的幂等性设计做到位。真能做到这一步,你在高并发系统设计上就已经跨过一道坎了。

还有一个最后的建议:如果团队不熟悉 ZK/etcd 的运维,优先选择 Redis 方案,但一定要用成熟的开源客户端,不要自己从底层去撸造轮子。分布式锁这个领域,坑已经被踩得很透了,站在别人的肩膀上会省去很多踩坑的时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 1:37:24

全极化SAR图像分类实战:SNAP预处理与极化分解全流程解析

直接开场&#xff0c;不绕弯子。全极化SAR图像分类&#xff0c;说白了就是把雷达影像里每个像素归到地物类别里&#xff0c;比如农田、森林、水体、建筑。这事儿的难点不在分类算法本身&#xff0c;而在前处理——极化SAR数据比光学影像麻烦得多&#xff0c;又是定标又是滤波又…

作者头像 李华
网站建设 2026/9/16 1:37:07

过拟合怎么办?PyTorch正则化三件套实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 1:36:51

AI前端实战:TypeScript流式处理与状态管理工程实践

1. 这不是一份“面试速成指南”&#xff0c;而是一份9月8日启动的AI前端实战备战手记如果你准备在9月8号开始准备今年AI前端面试的话——这句话听起来像一句时间锚点&#xff0c;但背后藏着一个正在剧烈变形的战场。过去三年&#xff0c;前端面试还围着React生命周期、Vue响应式…

作者头像 李华
网站建设 2026/9/16 1:36:50

MATLAB圆柱绕流非结构化网格生成与质量控制

简介&#xff1a;本资源是一套面向计算流体动力学&#xff08;CFD&#xff09;初学者与MATLAB实践者的圆柱绕流二维网格划分教学包&#xff0c;聚焦流体力学仿真中关键的前处理环节——几何建模与网格生成。资源包含1个MATLAB脚本&#xff08;chushiwangge.m&#xff09;用于自…

作者头像 李华
网站建设 2026/9/16 1:36:20

极化码自适应CA-SCL译码:面向5G/6G实时信道的动态纠错技术

1. 什么是极化码自适应CA-SCL译码&#xff1f;它到底解决了什么问题&#xff1f;极化码自适应CA-SCL译码&#xff0c;不是某个“新出的APP”或“网红工具”&#xff0c;而是通信系统底层物理层中一项关键的纠错译码技术。如果你拆开5G基站、卫星通信终端、或者新一代Wi-Fi 7芯片…

作者头像 李华
网站建设 2026/9/16 1:35:49

3个坑揭秘:第一代网站建设技术过时?建站报价怎么避坑

3个坑揭秘:第一代网站建设技术过时?建站报价怎么避坑 网站做好了没人访问,这是90%老板最头疼的事。你花了几万块【建站报价】,页面做得挺花哨,结果后台一看,流量全是零。别急着怪搜索引擎,很多时候问题出在底层架构上。很多人还在用“第一代网站建设技术”的思维去理解现在的互联网,觉得只要把图片放上去、把文…

作者头像 李华