资讯动态

分布式锁从理论到落地:选型、实现与踩坑全解析

发布时间:2026/10/22 3:02:57 来源:尧图企业网站定制
如果有人拿“分布式锁”这个项目来找我点评我大概率会先问他一个问题你能不能在十分钟里把锁从理论到落地、从选型到踩坑完整讲一遍说实话最近帮几位朋友过简历上的项目看了好几份跟分布式锁有关的代码和设计文档真心觉得这个题目被写烂了但真正讲透的人不多。分布式锁本质上就是一套让多个服务实例对同一个共享资源互斥访问的协调机制。单机锁在单体架构里够用服务一拆、实例一多单机锁就管不住跨进程的并发访问了。这篇文章我会从一场真实故障讲起把分布式锁的选型、核心实现、经典难点、面试高频问题全部梳理一遍。准备做这个项目、或者正在复习分布式基础的朋友应该能从中拿到一套能直接拿去讲、拿去写的完整思路。1. 为什么需要分布式锁从一段踩坑经历说起1.1 一场由并发导致的线上事故我之前参与过一个库存扣减相关的服务早期单体应用部署在一台机器上所有请求都进同一个进程。扣库存的逻辑用synchronized包了一层压测、上线都挺稳。后来业务量上来单机扛不住就把服务横向扩容到了三个实例前面挂负载均衡。结果上线当天晚上运营做了一波秒杀活动库存直接超卖。那个场景我记得很清楚数据库里某件商品库存剩最后 1 件两个用户同时提交订单两个请求被负载均衡分发到两台不同的实例上。两个实例各自执行了“查库存、扣库存、写库存”这段逻辑都读到库存是 1都执行了减一最后库存变成了 0但订单却生成了两条。事后排查问题非常典型。synchronized锁住的对象只在当前 JVM 进程内有效两个实例各持有一把属于自己的锁它们之间根本感知不到对方的存在。你锁你的我锁我的两个线程同时进了临界区。这就是单体锁在分布式环境下的第一个盲区。我后来经常拿这个例子跟人解释分布式锁的必要性。不是为了追新是架构演进到多实例之后绕不开这个问题。1.2 单机锁为什么失效要说清楚分布式锁得先想明白单机锁依赖的是什么。拿 Java 里的synchronized和ReentrantLock来说它们的核心依赖是 JVM 内存模型里的监视器对象以及操作系统提供的互斥原语。锁的标记、等待队列、线程阻塞唤醒这些状态全部存放在当前进程的内存空间里。换一句话说锁的有效范围就是这个 JVM 进程。问题来了分布式系统里多个实例是多个独立进程跑在不同机器上。A 实例里的锁对象B 实例根本看不见。你用synchronized保护了一段代码A 实例的线程进入临界区时B 实例的线程照样能进。两个进程各自以为自己是唯一持有者共享资源就被并发修改了。要解决这个问题锁的状态必须从“进程内存”里搬出来放到一个所有实例都能访问的公共存储里。可以是数据库可以是 Redis也可以是 ZooKeeper、etcd 这类协调服务。锁的标志位、持有者信息、过期时间全都放在这个公共组件上。每个实例在加锁的时候都去这个公共组件上竞争同一个标志位谁抢到了谁才拥有执行权限。这就是分布式锁的基本模型。1.3 分布式锁的三条底线分布式锁看起来就是个“多实例版的加锁操作”但它真正难的地方在于这套机制必须同时满足三个条件少一个都会出事。第一是互斥性。任何时刻只能有一个客户端持有锁。这个听起来理所当然但在主从架构、网络分区、GC 停顿这些场景下互斥性是很容易被打破的。第二是防死锁。持有锁的客户端崩溃了、网络断开了锁必须能自动释放不能无限期占着。所以 Redis 锁要设置过期时间ZooKeeper 依赖会话超时etcd 依赖租约机制本质上都是在处理这个问题。第三是容错性。锁服务本身不能成为单点。如果提供锁的节点挂了系统要能继续对外提供服务或者至少能快速切换到可用节点而不是整个业务链路瘫痪。这三个条件听起来简单实际配合起来非常微妙。比如互斥性和容错性在某些方案里就是矛盾的。主从切换的时候为了容错新主节点可能没有同步到旧主的锁记录结果另一个客户端就加锁成功了互斥性就被破坏了。这种矛盾在选型的时候会反复出现后面我会细讲。2. 分布式锁的技术选型四条路线怎么选2.1 数据库锁最原始但依然有效的方案先聊最朴素的路子用数据库来实现锁。做法大致有三种各有各的适用场景。第一种是基于唯一索引。在数据库里建一张锁表比如lock_id设唯一索引。客户端要加锁就往里插入一条记录插入成功说明拿到了锁插入失败说明锁被别人持有。释放锁就是删除这条记录。为了防止持有者崩溃后锁不释放还需要加一个expire_time字段后台定期清理过期记录。第二种是基于for update行锁。在事务里执行select ... for update数据库的行锁机制天然保证了同一时间只有一个事务能锁住这条记录。注意事务要提交锁才会释放所以业务逻辑必须和事务边界对齐。第三种是乐观锁。给数据表加一个version字段更新时带上版本号update ... set version version 1 where version ?更新影响行数为 0 就说明冲突了重试。数据库方案最大的优点是好理解、不引入新组件团队里任何一个后端都看得懂。但缺点也很明显性能受限。每次加锁解锁都是一次数据库 IO高并发场景下数据库会成为瓶颈。而且如果用的是读写分离架构主库写入锁记录、从库没同步同样有互斥失效的问题。我的评价是数据库锁适合低频、对性能不敏感的内部系统比如定时任务调度、低频对账任务。秒杀、抢购这种高并发场景别拿数据库硬扛。2.2 Redis锁性能与复杂度的平衡点Redis 锁是目前应用最广泛的方案核心原因是性能好而且实现起来足够简单。基本思路是使用 Redis 的原子操作SET key value NX EX同时设置过期时间。NX保证 key 不存在时才设置成功EX设置过期秒数。key 可以作为锁的唯一标识value 存客户端唯一标识防止误删别人的锁。Redis 锁的优点是加锁解锁速度极快单实例 Redis 的 QPS 可以到十万以上能扛住绝大多数业务场景。配套设施完善Redisson 这类客户端库已经封装好了看门狗续期、可重入、公平锁等高级能力开发者不需要从零实现。但 Redis 锁有一个绕不开的软肋一致性不是强保证。单实例 Redis 只提供主从异步复制如果主节点刚写入锁还没来得及同步给从节点就宕机了另一个客户端可能在新主节点上加锁成功。这个场景就是经典的“主从切换丢锁”问题。我在实际项目中的体会是Redis 锁适合大部分业务场景尤其是对中间件使用比较熟练、能接受极低概率锁失效的团队。如果你的业务对一致性要求极高比如涉及资金扣减、库存强校验那就需要更谨慎的评估或者直接选用强一致方案。2.3 ZooKeeper/etcd锁一致性优先的强方案ZooKeeper 和 etcd 能提供分布式锁核心原因在于它们本身是强一致性的协调服务。ZooKeeper 的实现思路是临时顺序节点。客户端在锁目录下创建一个临时有序节点然后判断自己创建的节点是不是当前最小的那个。如果是就认为自己获得了锁如果不是就对前一个节点设置监听等前一个节点删除后再重新判断。会话超时或者客户端主动删除节点锁都会自动释放。etcd 的实现思路类似依赖的是租约机制和 revision 版本号。客户端绑定一个租约创建 key租约过期 key 自动消失。多个客户端竞争同一个 key只有创建成功的那个能拿到锁。注意 etcd 的 key 版本号可以用来做排队也能避免锁误删。这两个方案的优点是共识算法保证了锁状态的一致性不像 Redis 那样存在主从切换时的丢失窗口。缺点是吞吐量比 Redis 低而且需要引入新的组件运维成本高。如果公司没有现成的 ZooKeeper 集群或者 etcd 集群为了一把锁引入一套新系统代价是不小的。对于需要强一致锁的场景例如分布式事务协调、跨机资源调度我会优先考虑 etcd。它的客户端库比 ZooKeeper 的 Curator 更简洁而且租约续期机制清晰代码也好维护。2.4 我的选型参考表把四种方案放在一起对比一下选型的时候照着这个表看就行。方案实现复杂度性能一致性可用性风险适用场景数据库唯一索引/乐观锁低低弱存在主从同步延迟数据库可用性低频任务、内部系统Redis SETNX Lua低高弱主从切换可能丢锁主从切换丢锁高并发、大部分业务ZooKeeper 临时顺序节点中中强会话超时误释放强一致、低频协调etcd 租约 revision中中强租约续期失败强一致、容器化服务这里补充一个个人观点选型不是越强越好。我之前处理过一个项目明明用 Redis 锁绰绰有余但架构师坚持上 ZooKeeper理由是“更可靠”。结果业务高峰期 ZK 集群压力大反而成了瓶颈。锁的可靠性要跟业务容忍度挂钩资金类业务可以牺牲性能和复杂度换取强一致普通业务用 Redis 锁加合理的保护措施就够了。3. Redis分布式锁的落地实现从SETNX到完整工程3.1 第一阶段SETNX EXPIRE踩坑的开始很多初学分布式锁的人第一版代码都长这样Boolean success redisTemplate.opsForValue().setIfAbsent(lockKey, 1); if (success) { redisTemplate.expire(lockKey, 30, TimeUnit.SECONDS); try { // 业务逻辑 } finally { redisTemplate.delete(lockKey); } }这段代码一眼就能看出两个问题。第一个问题setIfAbsent和expire是两条独立的命令它们之间不是原子的。如果加锁成功后执行expire之前服务进程崩溃了那这个 key 就永远不会过期锁永久失效。其他客户端永远加不了锁这就是死锁。第二个问题用固定的字符串1作为 value 是危险的。假设 A 获得锁后业务执行时间过长锁过期被 B 拿走了此时 A 执行完了想释放锁直接delete会把 B 的锁删掉。这属于典型的锁误删事故。正确写法应该一条命令搞定加锁和过期Boolean success redisTemplate.opsForValue() .setIfAbsent(lockKey, lockValue, 30, TimeUnit.SECONDS);这样加锁和设置过期时间是一个原子操作直接从根上避免了先加锁再设超时的崩溃窗口。value 也不再是固定值而是每个客户端实例生成的唯一标识为后面安全释放锁做好准备。3.2 第二阶段用Lua脚本保证释放锁的原子性释放锁的时候标准操作是先判断 value 是不是自己的再决定要不要删除。这个“判断 删除”也必须原子完成。如果分两步同样会踩竞态条件A 判断 value 是自己的但还没来得及删除锁就过期了B 抢到了锁A 再执行删除删掉的又是 B 的锁。Redis 提供了 Lua 脚本能力可以保证脚本内的多条命令原子执行。释放锁的 Lua 脚本很简单if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end用 Java 的DefaultRedisScript执行这段脚本就避免了“判断和删除分离”的问题。DefaultRedisScriptLong unlockScript new DefaultRedisScript(); unlockScript.setScriptText( if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end ); unlockScript.setResultType(Long.class); Long result redisTemplate.execute(unlockScript, Collections.singletonList(lockKey), lockValue);Lua 脚本是 Redis 分布式锁落地过程中非常关键的一环。很多项目中加锁都已经改成原子命令了但释放锁还是先 get 再 del隐患非常大。我在代码评审里看到这种写法一般都会直接标红。3.3 第三阶段看门狗续期解决业务超时问题加锁的时候需要设置过期时间但过期时间设多少是个难题。设短了业务还没执行完锁就过期了其他客户端趁虚而入等于没有锁。设长了万一持有锁的客户端崩溃了锁要很久才能自动释放后面的请求都会阻塞。一个相对合理的方案是给锁加自动续期机制也就是 Redisson 的看门狗思路。Redisson 默认给锁设置 30 秒过期后台起一个定时任务每隔 10 秒检查一次只要锁还持有在自己手里就把过期时间重新刷回 30 秒。业务执行完后手动释放锁并取消定时任务。如果客户端崩溃看门狗线程也停了锁会在最长 30 秒后自动过期不会死锁。自己实现一个简化版续期逻辑也不复杂大概长这样private void scheduleRenewal() { renewalTask scheduler.scheduleAtFixedRate(() - { String renewScript if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(pexpire, KEYS[1], ARGV[2]) else return 0 end; redisTemplate.execute(renewScript, Collections.singletonList(lockKey), lockValue, String.valueOf(expireTime.toMillis())); }, expireTime.toMillis() / 3, expireTime.toMillis() / 3, TimeUnit.MILLISECONDS); }注意续期的时候也要做 value 校验避免锁已经不属于自己了还在傻傻续期。真正的看门狗机制还包含锁所有权检查、锁计数等逻辑但核心思想就是这套“过期 续期”的组合拳。3.4 一个可参考的完整实现把上面几个阶段的思路整合起来就是一份可以落地的 Redis 分布式锁工具类。我用 Java 简化版描述一下核心结构细节可以根据自己项目的 Redis 客户端 API 调整。public class RedisDistributedLock { private final StringRedisTemplate redisTemplate; private final String lockKey; private final String lockValue UUID.randomUUID().toString(); private final Duration expireTime; private final ScheduledExecutorService scheduler Executors.newSingleThreadScheduledExecutor(); public RedisDistributedLock(StringRedisTemplate redisTemplate, String lockKey, Duration expireTime) { this.redisTemplate redisTemplate; this.lockKey lockKey; this.expireTime expireTime; } // 尝试加锁支持等待时间 public boolean tryLock(Duration waitTime) { long waitMillis waitTime.toMillis(); long start System.currentTimeMillis(); while (true) { Boolean success redisTemplate.opsForValue() .setIfAbsent(lockKey, lockValue, expireTime); if (Boolean.TRUE.equals(success)) { scheduleRenewal(); return true; } if (System.currentTimeMillis() - start waitMillis) { return false; } try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } } // 释放锁Lua脚本保证先校验再删除 public void unlock() { String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; DefaultRedisScriptLong unlockScript new DefaultRedisScript(script, Long.class); redisTemplate.execute(unlockScript, Collections.singletonList(lockKey), lockValue); scheduler.shutdownNow(); } // 按三分之一过期周期续期 private void scheduleRenewal() { scheduler.scheduleAtFixedRate(() - { String renewScript if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(pexpire, KEYS[1], ARGV[2]) else return 0 end; redisTemplate.execute(renewScript, Collections.singletonList(lockKey), lockValue, String.valueOf(expireTime.toMillis())); }, expireTime.toMillis() / 3, expireTime.toMillis() / 3, TimeUnit.MILLISECONDS); } }这里有几个细节要说明。lockValue用 UUID 生成保证每个客户端每次加锁尝试的标识不重复。释放锁用 Lua 脚本而不是直接 delete就是为了防止误删。续期周期取过期时间的三分之一是参考了 Redisson 的默认策略留出足够的时间余量避免网络抖动导致续期失败。实际生产环境中如果是 Java 项目我建议优先使用 Redisson因为它的实现经过大规模生产环境验证看门狗、重入、公平锁这些机制都已经很完善。自己写工具类主要适合学习阶段或者是公司没有引入 Redisson 的情况下做补充。4. 分布式锁的经典难点与源码级排查4.1 锁误删是怎么发生的锁误删是分布式锁最经典的坑我用一个具体场景把整个过程捋一遍。T 时刻客户端 A 加锁成功锁的 value 是 A 的唯一标识。 T10sA 执行业务时发生长时间 Full GC线程被暂停。 T30s锁过期Redis 自动删除这个 key。 T31s客户端 B 加锁成功value 是 B 的唯一标识。 T35sA 的 GC 结束业务执行完毕执行释放锁逻辑。如果释放锁写的是redisTemplate.delete(lockKey)那它删掉的就是 B 的锁。B 的业务还在执行中锁却已经被删了这时候客户端 C 又加锁成功两个客户端同时运行共享资源的互斥保护完全失效。解决办法就是我上面说的加锁时 value 放唯一标识释放锁时先用 Lua 脚本比较 value 再决定是否删除。这个校验过程必须是原子的不能拆成两步。在分布式系统工程里这种“先检查再操作”的竞态问题非常常见。从 Redis 锁到数据库更新到处都有它的影子。理解了这个案例很多并发问题的处理思路都能触类旁通。4.2 主从切换丢锁Redis锁的先天短板Redis 锁最大的隐患不是我上面说的误删而是主从切换导致锁丢失。Redis 的主从复制是异步的。客户端 A 在主节点上写入了锁主节点还没来得及把这条写操作同步给从节点主节点就宕机了。哨兵或集群机制会选举一个从节点升级为新的主节点但这个新主节点上没有 A 的锁记录。此时客户端 B 去加锁在新主节点上成功写入了锁。A 和 B 同时认为自己持有锁互斥性被彻底打破。这个问题的根源在于Redis 的主从切换优先保证可用性而不是一致性。它做不到“要么主从都有锁要么都没有锁”。业界对这个问题有一个争议比较大的方案叫 RedLock。思路是向多个独立的 Redis 节点依次加锁超过半数节点加锁成功才算加锁成功。这样即使部分节点故障锁记录仍然存在于多数节点上。但 RedLock 也有自己的问题包括时钟跳跃导致的锁过期时间不一致、客户端 GC 期间锁自然过期等这套方案在学术界和工业界都有不少讨论。我的观点是如果你的业务能接受极小概率的锁失效事件那 Redis 锁够用如果不能接受要么用强一致方案要么在业务层加幂等和乐观锁兜底。单纯靠 Redis 锁想实现绝对互斥从架构层面就走不通。4.3 GC停顿与锁过期怎么识别和处理这里再展开一下 GC 停顿的问题因为它在面试中经常被拿出来单独问。客户端 A 拿到了锁设置过期时间 30 秒。正当执行业务时JVM 发生了一次长时间的 Stop-The-World冻结了整整 30 秒。等 GC 恢复业务线程继续执行的时候锁已经过期了客户端 B 已经拿到了锁。A 还在执行临界区代码B 也进入了临界区两个线程同时操作同一份数据。这个问题本质上是“业务执行时间超过锁的持续时间”它跟看门狗有关系但也不完全是看门狗能解决的。看门狗只能保证正常执行时的续期但如果是 GC 停顿本身导致定时续期线程也无法运行那同样救不了。处理方式有几个方向。第一尽量缩短锁内代码的执行时间把耗时操作移出临界区。第二业务层做幂等设计即使锁失效操作重放也不会造成数据错误。第三数据库层加乐观锁兜底用版本号防止并发更新覆盖。没有一种分布式锁能做到绝对防御合理的架构是靠多层机制叠出来的安全网。4.4 可重入与公平性容易被追问的两个点再说两个容易被面试官追问的进阶话题。可重入指的是同一个线程在已经持有锁的情况下再次尝试获取同一把锁时应该直接成功而不是和自己死磕。Redis 原生 SETNX 不支持可重入同一个线程第二次加锁会被自己挡住。Redisson 的实现是给锁关联一个计数器每次重入加一释放时减一减到零才真正删除锁记录。自己实现也可以思路是用 Hash 结构存储持有者信息和重入计数。公平性指的是多个客户端竞争同一把锁时能否按照申请顺序依次获得锁。Redis 原生的 SETNX 没有排队机制谁的网络快、谁能第一时间写入谁就拿锁属于非公平锁。ZooKeeper 的临时顺序节点天然是公平的节点序号最小的先拿锁。Redisson 提供了 FairLock 实现用有序队列维护等待顺序。这里没有绝对的好与坏业务上偶尔会要求公平但大多数场景下非公平锁的吞吐反而更好因为省去了排队唤醒的开销。5. 面试与代码评审中的高频问题清单5.1 面试官常问的分布式锁问题如果你面试的时候把分布式锁放在项目亮点里下面这几个问题几乎必被问到。这里我给出回答思路相当于把全篇内容浓缩成核心要点。第一个问题为什么不能用 synchronized因为 synchronized 是 JVM 进程级的锁只在单实例内有效。分布式环境下多个实例分属不同进程锁状态不共享必须借助外部存储。第二个问题SETNX 和 SET NX EX 有什么区别SETNX 只设置 key要设置过期时间必须再发一条 EXPIRE 命令二者非原子。SET NX EX 是一条原子命令同时完成加锁和过期时间设置。第三个问题锁过期时间怎么设置没有万能答案。原则是尽量短配合看门狗续期。如果锁内业务是查库、算数这类快速操作1 到 5 秒可以接受。如果是调用外部接口最好评估 P99 耗时再定同时续期逻辑必须兜底。第四个问题业务执行时间超过锁过期时间怎么办答案就是看门狗续期机制。我自己的实现是每条锁关联一个后台续期线程每过期周期的三分之一续一次。也可以直接把锁内逻辑拆短根本不让它超时。第五个问题Redis 主从切换丢锁怎么办首先如实说明 Redis 锁存在这个先天短板然后讲业务容忍度判断。如果业务不能接受可以引入 ZooKeeper 或 etcd 强一致锁或者在业务层加数据库乐观锁兜底。不要一上来就吹 RedLock讲清楚它的争议和适用边界反而更显深度。第六个问题分布式锁和消息队列都能削峰能不能互相替代这个属于概念混淆。分布式锁解决的是并发互斥消息队列解决的是流量缓冲和异步解耦。两个东西解决的问题域不同不存在替代关系。5.2 代码评审时我重点看什么站在评审者的角度我看别人的分布式锁代码会按下面这个顺序逐项检查。第一key 的命名。锁的粒度是否合理。比如一个全局锁LOCK:ORDER和一个LOCK:ORDER:12345前者可能把所有订单的扣库存操作都串行化了吞吐会很难看。后者只锁单条订单并发度高。锁粒度是性能和安全性之间的经典取舍。第二value 是否唯一。是不是用了 UUID、线程 ID 一类的唯一标识。如果 value 是固定字符串说明释放锁时没有做所有权校验锁误删风险极高。第三过期时间是否存在。我看到过只 SETNX 不设过期时间的代码这属于死锁隐患。持锁进程一崩所有客户端永远拿不到锁。第四释放锁的逻辑是否是原子的。是否用 Lua 脚本完成“校验 value 删除 key”。如果没有代码评审这一项直接不通过。第五有没有续期逻辑。锁内业务逻辑比较长的场景里没有续期就意味着锁可能在业务执行中途过期互斥保护名存实亡。第六有没有降级和兜底方案。锁失效之后业务层有没有幂等处理数据库有没有乐观锁只孤零零一把 Redis 锁说明对异常场景考虑不足。这套 checklist 不光能用来评别人的代码自己写完分布式锁之后对着这个清单自查一遍也能发现不少问题。6. 一个重要提醒锁的粒度与并发度设计最后单独开一节说锁的粒度因为这是很多项目里最容易出问题的地方。分布式锁的粒度直接决定了系统的并发能力。举个例子有一个下单服务把所有订单的创建、库存扣减、优惠券核销全部放在一把全局锁里保护。从安全性角度看绝对没问题不会超卖也不会重复核销但代价是全部下单请求被强制串行化了QPS 天花板被钉死。这就是典型的“锁得住也锁死了”。更合理的做法是按业务维度拆锁。库存扣减按商品 SKU 维度加锁每个 SKU 一把锁优惠券核销按用户维度加锁每个用户一把锁订单幂等按订单号维度加锁。拆完之后不同 SKU 的扣减可以并行执行并发度大幅提升。还有一个容易被忽略的地方是锁的 key 设计。key 不是越短越好而是要包含足够的业务上下文。比如LOCK:STOCK:{skuId}比LOCK:STOCK多了维度信息便于排查和观测。但 key 也不要无限长过长的 key 会浪费 Redis 内存而且增加网络传输成本。我在实际项目中还遇到过一种情况锁粒度拆得很细但业务代码写得太粗暴一把锁拆成了三把结果三把锁之间执行顺序不一致产生死锁。比如 A 操作先锁订单再锁库存B 操作先锁库存再锁订单两个并发请求各自持有对方需要的锁互相等待整个链路就卡死了。分布式锁的死锁不只是 redis 过期时间的问题业务代码里锁顺序设计不当同样会产生死锁。建议所有持锁操作的顺序在代码层面保持全局一致这是一个比较隐蔽但实战中很容易踩的坑。分布式锁的粒度设计没有标准答案核心原则是够用就好。你加锁的唯一目的是保护临界区资源不受并发侵害在这个前提下尽可能让被锁住的代码块小一点、细一点。别为了省事给整个方法上个粗锁那等于用机器的吞吐换代码简洁账算不过来。我自己点评别人项目的时候最常问的一句话是你加这把锁到底保护的是什么最坏情况下锁失效了你的系统能扛得住吗前者帮你判断锁的设计是否合理后者帮你判断要不要引入更复杂的强一致方案。把这两个问题想明白分布式锁这个项目的含金量就出来了。技术方案可以替代但这个思考过程替代不了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑