news 2026/9/28 8:49:59

分布式锁四种主流实现方案:数据库、Redis、ZooKeeper、Etcd对比与选型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
分布式锁四种主流实现方案:数据库、Redis、ZooKeeper、Etcd对比与选型

老读者应该记得,去年我写过一遍单体应用里synchronized和ReentrantLock的对比,当时评论区就有人问:服务拆成多台机器部署之后,同一个用户请求落在不同实例上,JVM 锁还管用吗?答案显然是不管用。跨进程、跨节点的互斥,只能靠分布式锁。这两年我在订单防重、定时任务调度、缓存击穿治理这几个场景里,把市面上主流的四类分布式锁方案都趟了一遍,踩过的坑不算少,今天一次性把数据库、Redis、ZooKeeper、Etcd 这 4 种主流实现方式掰开揉碎讲清楚。这篇东西既是给准备上手分布式锁的工程同学看的,也是给那些正在准备分布式系统面试、想搞清楚 Redis 分布式锁与 ZK 分布式锁底层差异的同学准备的。

1. 先搞清楚:分布式锁到底锁住了什么

1.1 为什么单机锁到分布式就失灵了

单机环境下,我们希望一段代码在同一个进程内只能被一个线程执行,于是有了synchronized、ReentrantLock这些东西。它们的核心依赖是 JVM 内存里那份共享的锁状态,线程 A 加锁,线程 B 去读同一块内存发现锁被持有,就只能阻塞等待。这个模型成立的前提是“所有线程共享同一块内存”,一旦应用部署成多实例,内存就各自独立了,JVM 锁自然失效。

多实例部署现在几乎是标配,哪怕一个小服务,为了保证可用性也会至少部署两台。一台机器扛不住流量、一台机器宕机不能影响整体服务,这是最朴素的诉求。于是同一份请求可能被负载均衡分发到任意一台实例上,同一行库存数据也可能被多个实例同时操作。这个时候如果不做跨进程互斥,就会出现超卖、重复扣款、重复下发消息等一系列问题。分布式锁解决的就是这个层面的互斥问题。

1.2 一把合格的分布式锁需要满足什么

很多人一上来就写 Redis 命令,其实先想清楚分布式锁的评判标准,后面所有方案都能套这套标准去衡量。

  • 互斥性:任意时刻,只能有一个客户端持有锁。这是锁的底线,失去了互斥性,整个方案就没有存在意义。
  • 安全性:锁必须能被安全释放。如果持有锁的客户端崩溃了,锁不能一直挂着,必须有超时或者类似机制兜底,否则就是死锁。
  • 可用性:加锁、解锁的链路不能频繁出故障。分布式锁的基础存储组件(Redis、ZK、Etcd)本身要可用,同时锁服务不能因为少数节点故障就整体不可用。
  • 可重入性:同一个线程在持有锁的情况下能否再次加锁。这在递归调用或者同一线程内多次获取同一个锁的场景里很关键,具体业务里不一定会用到,但实现上要考虑。
  • 性能:加解锁延迟要低,不能对业务主链路造成明显开销。

后面讲四种方案时,我会反复拿这套标准去比划,你会发现没有银弹,每种方案都是在这些维度之间做取舍。

2. 基于数据库:最朴素但常常够用

2.1 唯一约束方案:忘掉锁,直接占用

数据库做锁的思路最早也最直接。第一种玩法是利用唯一约束,拿“插入成功即加锁成功,删除记录即释放锁”这种思路。建一张锁表,锁名做唯一索引:

CREATE TABLE `distributed_lock` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `lock_name` VARCHAR(64) NOT NULL, `owner` VARCHAR(128) NOT NULL, `expire_time` DATETIME NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_lock_name` (`lock_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

加锁就是尝试插入一条记录,插进去就是拿到锁,插不进去说明别人持有着。释放锁就是删掉这条记录。这个方案胜在极简,不需要引入任何额外组件,有一张表就能跑。但有个致命问题,如果持有锁的线程崩溃了,记录永远删不掉,锁就死了。所以表里我还加了expire_time字段,启动一个定时任务扫描超时记录并删除,作为兜底。

这个方案我很少在生产主线用,但在一些内部工具、低频后台任务里反而合适。举个例子,公司内部有个数据修复平台,每天凌晨跑一批补偿任务,任务本身要保证同一个业务维度不被两个运维同时操作,频率极低,用唯一约束表完全够了,不值得为这种场景上 Redis。

2.2 悲观锁方案:SELECT FOR UPDATE

第二种数据库方案是走事务 + 悲观锁。核心就一条 SQL:

SELECT * FROM distributed_lock WHERE lock_name = 'order_123' FOR UPDATE;

注意这张表里lock_name也要有唯一索引,FOR UPDATE会锁定这一行,直到事务提交或回滚。加锁流程是开启事务,执行上面这条 SQL,查到了就代表拿到锁,执行业务逻辑,提交事务自动释放锁。没查到就阻塞等待,InnoDB 的行锁会让它排队。

这个方案的好处是省去了自己管理锁记录,释放逻辑跟着事务走,一旦事务回滚或提交,锁自然释放,不会出现崩溃后锁残留的问题。缺点是性能受限于数据库连接数和锁等待时间,高并发下SELECT FOR UPDATE会把连接长时间占住,业务高峰期很容易打满连接池。数据库这种单点写入压力的方案,只适合低并发、强事务一致性的内部系统。而且操作的是数据库,如果数据库本身做了主从,锁记录在主库,所有请求都要打到主库上,读写分离架构下这个方案会破坏原有的流量规划。

2.3 数据库方案的优缺点和适用场景

数据库方案的优点一句话总结:不引入新组件、实现简单、事务天然保证一致性。没有额外维护 Redis、ZK 集群的成本,对团队规模小、基础设施薄弱的项目来说很有吸引力。但它撑不住高并发,加锁操作和业务操作耦合在同一个事务里时,事务时长直接影响锁持有时间,容易拖垮数据库连接。

就我的经验,数据库分布式锁适合用在并发量几百 QPS 以内的内部系统、后台管理任务、低频定时任务里。互联网高并发入口链路,不要用数据库做锁,扛不住。这个结论在我维护过的多个项目中反复被验证,凡是拿 MySQL 做锁扛流量入口的,最后都不得不迁移到 Redis 方案。

3. 基于Redis:性能最强但细节最险

3.1 SET NX EX 一条命令搞定

Redis 做分布式锁是现在最主流的方案,因为它性能好、实现轻。公认的标准写法是:

SET lock_key unique_value NX PX 30000

这条命令做了三件事:NX保证只有键不存在时才能设置成功,PX 30000给键加上 30 秒过期时间,unique_value作为持有者标识。为什么必须一条命令?因为如果先SETNX再单独EXPIRE,两步之间进程崩溃的话,锁会永久残留。这是最经典的坑,网上被讲了无数次,但我接手过的代码里仍然能看到两段式的写法,看完只能说很心痛。

释放锁的时候,直接用DEL是危险的。考虑这个场景:线程 A 拿到锁,执行时间超过了 30 秒,锁自动过期了,线程 B 拿到同一把锁开始执行,此时 A 终于跑完了,顺手DEL把 B 的锁给删了,后面 C 又进来了,锁完全乱套。正确的释放必须校验持有者身份,这个要求用 Lua 脚本实现原子操作:

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

unique_value一般是 UUID 加上线程 ID,保证唯一性。每次释放锁之前先 GET 校验再 DEL,整个过程用 Lua 封装成原子操作,防止校验和删除之间被别人插一脚。这一步是 Redis 分布式锁最容易忽略的细节,绝不能用两步 Java 代码去实现“先判断再删除”。

3.2 超时释放与续期:Redisson看门狗

刚才那个 30 秒过期时间,其实是个天大的麻烦。业务执行得慢,30 秒不够怎么办?锁自动释放了,别的线程进来了,互斥被打破。把过期时间设得很长?万一持有者真崩了,锁要挂很久才能被回收,可用性受损。怎么解决?续期机制。这就是 Redisson 的价值所在。

Redisson 是 Java 生态最成熟的 Redis 分布式锁客户端,我用它可以做到这样:

RLock lock = redissonClient.getLock("order:123"); // 尝试加锁,最多等3秒,锁超时30秒 if (lock.tryLock(3, 30, TimeUnit.SECONDS)) { try { // 业务逻辑 } finally { lock.unlock(); } }

这里传入的leaseTime其实很少用。Redisson 默认还有一个看门狗机制(Watchdog):如果没有显式传超时时间,Redisson 会默认给锁设置 30 秒过期,然后启动一个后台定时任务,每 10 秒检查一次,如果锁还被当前线程持有,就自动把过期时间重置回 30 秒。这个机制解决了“业务没跑完锁先断了”的问题。

我实际项目里踩过一个坑:锁内做外部接口调用,对方响应超时设的 60 秒,看门狗把锁续到 90 秒,接口卡住后线程一直占着锁不释放,打得 Redis 满满都是槽位被占的报错。后来我给所有外部调用的锁显式设置了leaseTime,并保证这个值大于外部调用最大超时时间,看门狗不再介入,锁的时长由我控制。所以我的建议是,业务代码不复杂就放心用看门狗,复杂链路务必显式控制锁时间,哪怕多留些余量,也比无限续期好。

3.3 RedLock与主从切换的坑

Redis 分布式锁还有一个著名的争议点:主从架构下的锁丢失问题。Redis 主从同步是异步的,线程 A 给 master 写入了锁,master 还没来得及把数据同步给 slave 就宕机了,哨兵把 slave 提升为新 master,此时新 master 上压根没有这把锁。另一个线程 B 来加锁,直接成功了,互斥失败。

为了应对这个问题,Redis 作者提出了 RedLock 算法。思路是部署多个互相独立的 Redis 节点,客户端向超过半数的节点申请加锁,只有超过半数成功才算加锁成功。理论上只要不是超过半数的节点同时宕机,锁就安全。这个方案在分布式系统领域争议很大,很多大牛发文反对,核心论点在于它依赖了不可控的时钟和 GC 停顿。但就工程实践看,当业务对安全要求极高、又不想引入 ZK 这种强一致组件时,RedLock 仍然是一个值得了解的方案。

我自己用过 Redisson 的RedissonRedLock写过一版实验代码,过程并不复杂,但部署成本高,需要独立的 Redis 节点,而且加锁时间随节点数量线性上升。我的观点是:如果你的 Redis 本身就配置了正确的持久化(AOF + 合理 fsync 策略),并且不是很极端的高并发场景,普通 Redis 分布式锁已经够用。真正需要 RedLock 的场景,大概率也到了该认真考虑 ZK 或 Etcd 的时候了。

3.4 Redis方案的使用场景与注意点

Redis 分布式锁最常用的场景是缓存击穿治理、秒杀库存扣减、幂等控制这类对性能和一致性都有要求的入口链路。拿秒杀场景举例,用户请求量每秒上万,库存扣减如果走数据库悲观锁,数据库直接被打死;走 Redis 锁,先抢锁再扣库存,抢不到的直接返回“已售罄”,整体响应时间能控制在几十毫秒。

用 Redis 做锁,有几个注意点值得记下来:

  • 过期时间必须远大于单次业务执行时间,拿不准就设置一个相对大的值,配合看门狗续期。
  • 锁的粒度要细。不要让多个业务共用一把锁,锁的 key 要能标识唯一资源,比如用户维度锁用user:123,订单维度锁用order:123,粒度越细并发度越高。
  • Redis 本身的持久化策略不能省。关闭持久化的 Redis 重启即丢数据,锁也会丢,主从架构下更危险。生产环境至少开启 AOF,建议appendfsync everysec,兼顾性能与安全。

4. 基于ZooKeeper:一致性优先的可靠选择

4.1 临时顺序节点+Watch监听

ZooKeeper 实现分布式锁依赖的是它底层的 ZAB 协议,数据在集群节点间强一致同步,不存在 Redis 那种主从切换丢数据的问题。ZK 的锁模型基于多层节点机制:

  • 持久节点:会话结束后依然存在。
  • 临时节点:会话结束后自动删除。
  • 顺序节点:创建时自动追加递增序号。

ZK 分布式锁的经典实现是创建临时顺序节点。客户端 A 在/locks/order_123下创建临时顺序节点/locks/order_123/lock_0000001,这时候它发现自己创建的节点序号最小,就认为自己拿到锁了。客户端 B 同样创建节点,发现序号不是最小,于是对自己前面那个节点注册 Watch,进入等待状态。当前一个节点被删除时,B 被唤醒,检查自己是否已经是序号最小的节点,如果是就持有锁。

这里的“前一个节点被删除”,就是持有锁的客户端会话结束、临时节点自动消失。核心优势是:持有锁的客户端如果宕机,ZK 会检测到会话超时并自动清理临时节点,锁自动释放,不需要额外设置超时时间,也避免了 Redis 那种“锁过期但业务没跑完”的纠结。临时节点的生命周期与会话绑定,这是 ZK 锁最优雅的设计。

4.2 Curator InterProcessMutex

自己用 ZK 原生 API 写锁,要考虑的事情不少:创建节点、注册监听、处理监听事件后的重试、会话重连后的状态恢复。生产环境不推荐自己造轮子,直接用 Curator 封装好的InterProcessMutex。

CuratorFramework client = CuratorFrameworkFactory.newClient( "zk1:2181,zk2:2181,zk3:2181", new ExponentialBackoffRetry(1000, 3)); client.start(); InterProcessMutex lock = new InterProcessMutex(client, "/locks/order_123"); if (lock.acquire(3, TimeUnit.SECONDS)) { try { // 业务逻辑 } finally { lock.release(); } }

Curator 的InterProcessMutex有几个特性值得了解。它是可重入的,同一个线程可以多次acquire,对应每次都要release。它基于临时顺序节点实现,加锁过程会创建一个带UUID的临时顺序节点,至于取哪个节点作为锁标识,Curator 会在内部处理。它支持阻塞获取和限时获取,超时返回 false,给业务方灵活的选择空间。另外 Curator 还提供了InterProcessReadWriteLock读写锁,在某些读多写少的场景里能进一步提升并发度。

4.3 羊群效应与性能瓶颈

ZK 分布式锁并非没有缺点。一个是性能,ZK 加锁需要通过网络通信创建节点,整个过程要走 ZAB 协议确认,延迟通常在几毫秒到几十毫秒之间,和 Redis 的亚毫秒级相比有明显差距。另一个是羊群效应,这是 ZK 锁的经典问题。早期实现是让所有等待线程都监听锁持有者的节点,锁一释放,所有等待线程同时被唤醒,大家一起抢锁,最终只有一个成功,其余线程继续等待。节点数量多时,这种惊群会带来大量无谓的网络和内存开销。

解决方案就是上面说的临时顺序节点 + 监听前一个节点,把锁的等待变成链式传递,每个客户端只监听它前面的那个节点,释放时只通知一个等待者。Curator 的实现就是这样做的,这也是为什么我强烈建议大家直接使用成熟客户端,而不是自己写原生实现。

ZK 方案最适合对一致性要求极高、但对性能要求没那么极端的场景。比如分布式任务调度中心的 leader 选举、配置中心的变更发布、金融系统里面的资金对账任务。在这些场景里,锁的可靠性和自动释放能力比加锁延迟更重要。

5. 基于Etcd:云原生时代的后起之秀

5.1 Lease + Revision + Watch机制

Etcd 是云原生技术栈里的明星组件,Kubernetes 内部大量使用它做数据存储,其分布式锁能力也相当完善。Etcd 实现分布式锁依赖三个核心机制:

  • Lease(租约):类似于 Redis 的过期时间。客户端创建一个租约,指定过期时间,到期后租约自动失效,关联的 key 会被自动删除。租约可以续期,防止业务未完成时锁提前释放。
  • Revision(版本号):Etcd 中每个 key 的每次修改都会产生一个全局递增的 revision,分布式锁的竞争就通过比较 revision 的先后顺序来确定。
  • Watch(监听):客户端可以 Watch 一个 key,当 key 发生变化(删除、修改)时收到通知。

Etcd 锁的实现思路和 ZK 的临时顺序节点非常像。客户端创建一个 key,比如/locks/order_123/xxx,其中xxx是客户端持有的租约 ID 加上唯一标识。在同一个锁前缀下,所有客户端创建的 key 都有不同的 revision,revision 最小的那个客户端就是持锁者,其他客户端 Watch 前一个 key,等它被删除。

具体流程大致是:客户端 A 创建 key,发现它的 revision 是最小的,直接持有锁;客户端 B 创建 key,发现前面还有一个 revision 更小的 key,注册 Watch 到那个 key 上;A 的租约到期或者 A 显式释放锁(删除自己的 key),B 收到 Watch 事件,再次检查自己是不是 revision 最小的,如果是就拿到锁。整个机制和 ZK 版如出一辙,但底层依赖的是一个更契合云原生场景的存储组件。

5.2 etcd 分布式锁的代码示例

Go 生态里,etcd 官方提供concurrency包,里面封装了分布式锁的实现。Java 生态里对应的客户端是jetcd,它也有类似 Lock 接口。Go 的代码大致长这样:

cli, _ := clientv3.New(clientv3.Config{ Endpoints: []string{"etcd1:2379", "etcd2:2379", "etcd3:2379"}, }) session, _ := concurrency.NewSession(cli, concurrency.WithTTL(10)) mutex := concurrency.NewMutex(session, "/locks/order_123") mutex.Lock(context.TODO()) defer mutex.Unlock()

这里的关键点是concurrency.NewSession创建了一个带 TTL 的会话,会话到期后自动释放锁,这是 Etcd 版锁的兜底机制。NewMutex内部使用了一个租约,把锁 key 关联到这个租约上。实际使用中留意两个问题:

  • 一个 Session 可以服务于多个 Mutex,但一把锁最好关联一个租约,避免租约被意外的过期操作影响。
  • Lock是阻塞获取的,如果业务有超时控制需求,使用TryLock或者通过 context 传递截止时间。

Etcd 锁的可靠性和 ZK 相当,因为 Etcd 的 Raft 协议同样保证了数据在集群中多数派节点的持久化和一致性。但 Etcd 的部署和维护成本比 Redis 高,比 ZK 略低(因为是云原生标配,K8s 集群里往往已经有现成的 Etcd)。如果公司已经在用 K8s,Etcd 锁几乎是顺带可用的能力。

5.3 Etcd 与 ZK 的对比

把 ZK 和 Etcd 放一起比较,是面试官非常爱问的话题。两者的一致性协议不同,ZK 用的是 ZAB,Etcd 用的是 Raft,但都提供线性的强一致读写,锁语义的可靠性在同一水平。有几个实际差异值得关注:

  • 运维成本:Etcd 生态更现代,和 K8s 天然集成,云原生环境里基本免运维。ZK 是一个独立组件,需要额外部署监控和维护,心智负担更重。
  • 客户端成熟度:ZK 的 Curator 在 Java 生态里非常成熟,API 设计完善,使用体验好。Etcd 的 Java 客户端jetcd相对年轻,功能完整度和 Curator 还有差距,但 Go 生态的concurrency包做得很好。
  • 性能:两者都属于强一致组件的范畴,加锁延迟在同一量级,都比 Redis 慢。如果锁成为热点,两者都会成为瓶颈。
  • 功能范围:ZK 还提供命名服务、配置管理、服务发现,Etcd 在 K8s 场景里主要负责配置存储。选哪个更多取决于你所在的技术栈里已经有什么。

我的感觉是,如果团队是 Java 技术栈、已有 ZK 运维经验,继续用 ZK 没任何问题。如果是从零起步、基础设施以云原生为主,选 Etcd 更合理,一举两得,不用多维护一套组件。

6. 四方案对比与选型建议

6.1 核心指标对比表

四种方案的特点我汇总成一张表,方便直观对比:

维度数据库RedisZooKeeperEtcd
性能低高中中
一致性强(单库)弱(异步复制)强强
自动释放需定时任务兜底过期时间/看门狗会话超时租约到期
锁丢失风险低主从切换时可能丢失低低
可重入需自行实现Redisson 支持Curator 支持客户端支持
引入成本零低中中
适用并发量千级以下万级以上千到万级千到万级

真要我给个一句话建议,那就是:图省事加性能,项目里已有 Redis,选 Redis;要强一致且已有 ZK 运维设施,选 ZK;在云原生环境里从零选型,选 Etcd;只是给内部工具做简单互斥,数据库就行。

6.2 按业务场景选型的具体建议

  • 秒杀、抢购、库存扣减:高并发、高吞吐,首选 Redis 分布式锁,注意配合 Lua 释放锁脚本和合理的过期时间。这类场景遇到锁冲突,更好的做法是直接快速失败返回,不要让线程阻塞排队。
  • 定时任务调度:多实例部署时同一个定时任务只会执行一次。我会选 ZK 或 Etcd,因为定时任务频率低,对加锁性能不敏感,但对可靠性要求高,不能让同一批次的任务被两台机器重复执行,否则可能出现重复对账、重复发券。
  • 缓存重建:热点缓存失效后,大量请求同时回源数据库。用 Redis 锁做互斥,拿到锁的线程去查库回填缓存,拿不到的线程快速返回旧缓存或者短暂等待再读取,整体效果很好。
  • 配置更新与 leader 选举:配置更新要求绝对一致,不能出现两个节点同时执行变更。ZK 或 Etcd 的强一致模型在这里最合适,Redis 方案在这种场景里我是不推荐的。
  • 内部管理平台:数据订正、批量任务触发,并发量小且允许线程短暂阻塞。数据库方案足够,省掉一套组件的维护成本。

6.3 从锁的粒度看选型差异

除了场景和并发量,锁的粒度也影响选型。比如分布式锁的 key 设计为细粒度时,每个 key 的竞争频率低,Redis 的极高性能优势能充分发挥。但如果锁粒度粗,比如整个店铺的所有操作共用一把锁,所有请求都串行化,任何组件的性能优势都救不了这种设计。无论选哪种方案,锁粒度设计永远是第一位的。这个观点我在代码评审里强调了无数次,很多人写着写着就把所有业务共用一个锁前缀,并发能力直接砍半,然后怪锁的方案选错了。

7. 高频面试追问与避坑实录

7.1 关于分布式锁的高频追问

分布式锁在面试里出现频率极高,而且面试官往往不满足于“我知道有四种方案”,而是层层深挖。我整理了十几个追问,覆盖了不同深度:

  • 为什么数据库唯一约束可以做锁?它的失效场景有哪些?
  • SET NX EX和SETNX + EXPIRE有什么区别?为什么必须一条命令?
  • Redis 分布式锁释放时为什么要用 Lua 脚本?不用会怎样?
  • Redis 锁的过期时间怎么设置?业务没执行完锁过期了怎么办?
  • Redisson 看门狗的原理是什么?续期是精确续还是每次都重置?
  • 为什么说 Redis 主从模式下锁可能丢失?如何解决?
  • RedLock 的加锁流程是什么?它有争议吗?
  • ZK 分布式锁的底层实现原理是什么?为什么用临时顺序节点?
  • 什么是羊群效应?ZK 锁如何避免?
  • ZK 锁如果客户端长时间阻塞,锁会被自动释放吗?为什么?
  • Etcd 分布式锁与 ZK 分布式锁的异同?
  • 四种方案各自的优缺点和适用场景是什么?
  • 你项目里实际上用过哪种方案?遇到过什么问题?

其中“你项目里用 Redis 锁遇到过什么问题”这一题,是区分候选人是真用过还是背概念最好的试金石。我自己真实遇到过的就是锁过期导致互斥失效和持有者身份校验没做好导致误删锁,这两件事如果你在面试里能讲出当时的排查过程和处理方案,面试官基本就认可你确实有实战经验了。

7.2 实测中遇到的典型问题和排查思路

分享几个真实项目中踩过的坑,给大家做个参考。

第一个是锁过期与业务耗时打架。排查线上问题时发现限时抢购的库存偶尔会超扣,把日志拉出来一比对,时间戳差了几百毫秒的两个线程同时进入了扣减逻辑。根因是业务里查了第三方库存接口,单次耗时超过锁过期时间,前一个线程的锁已经过期,后一个线程加锁成功并进入。解决方案有两层:第一层把锁过期时间从 10 秒调整到 30 秒,给业务留足余量;第二层在扣库存前二次校验当前操作是否仍然持有锁。这两层叠加才解决了超扣问题。更合理的做法是缩短锁内执行业务的链路,把外部调用移出锁的范围。

第二个是忘了释放锁。代码里tryLock成功之后,业务抛了一个没有被捕获的异常,finally块里没写unlock,这把锁挂了接近一分钟直到过期。排查时 Redis 里看到order_4421这个 key 长时间存在,TTL 一直在刷新,明显是被某个线程反复续期,但业务日志里并没有对应的成功记录。后面养成了条件反射,加锁必带try { } finally { },代码评审也会重点检查这个点。看门狗能续期,但不代表你可以不释放,锁的释放和连接的关闭一样,必须作为底线纪律。

第三个是锁粒度太粗。业务里把一个用户的所有操作都放到了同一个 key 上,用户同时下单和查询优惠券,都必须抢同一把锁。结果 QPS 稍微上来,Redis 锁超时重试的比例直线上升。排查后把锁 key 从user:123细化到了user:123:order_create和user:123:coupon_query,并发能力瞬间恢复了。心态上要明确,锁保护的是共享资源的写操作,读操作如果能容忍短暂不一致,完全不需要加锁。

7.3 一点实践心得

分布式锁这个东西,方案本身难度不大,真正的复杂度全藏在细节里。我历经这几个项目后最深的体会是:不管你选哪套方案,都要把锁的过期、续期、释放、校验这四个环节想清楚。很多线上问题不是锁“设计错”了,而是这四件事里有一件没考虑到位。方案选型上,优先用团队已经熟悉的组件,不要为分布式锁单独引入一套新基础设施,除非业务真的需要它的强一致特性。另外建议配置一个锁监控的指标,把加锁耗时、持有时间、等待时间这几个数值收集起来,方便在出问题时快速定位。分布式锁没有全能的解,只有适合当下场景的选择,而判断“适合”这件事,靠的还是对业务场景和背后原理的深刻理解。

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

储能BMS开发中的MBD:从模型设计到自动代码生成全解析

这几年储能行业有多火,不用我多说。但如果你真去储能企业走一圈,会发现一个有意思的现象:同一个“BMS开发工程师”的岗位,有的公司要求你会写C代码,有的公司要求你会搭Simulink模型,还有的公司要求你既能搞…

作者头像 李华
网站建设 2026/9/28 8:49:58

C++装饰器模式实战:对象所有权与调用链路的避坑指南

提到C里的装饰器模式,很多人第一反应是Java那套IO流——FileInputStream外面套一层BufferedInputStream,再套一层DataInputStream。但真到了C里,你会发现事情没那么简单:没有interface关键字,没有内建的注解机制&#…

作者头像 李华
网站建设 2026/9/28 8:49:42

AI Agent安全边界实战:从Opus 5红队演练到OpenAI Codex配置加固

1. 从"Opus 5黑了OpenAI"这个标题说起:一场关于AI安全边界的真实演练看到"离谱,OpenAI被Opus 5黑了!"这个标题,我第一反应不是震惊,而是好奇——这到底是一次真实的安全事件,还是一个精…

作者头像 李华
网站建设 2026/9/28 8:48:59

PWM驱动MOS管H桥设计指南:从原理到电机控制实战

1. 从零搞懂PWM驱动MOS管H桥:为什么它是电机控制的核心搞电机驱动的朋友,绕不开一个经典电路拓扑——H桥。不管你是做智能小车、机械臂关节、还是电动工具,只要涉及直流电机的正反转和调速,H桥加PWM的组合几乎是标配方案。我这些年…

作者头像 李华
网站建设 2026/9/28 8:48:10

长期投资的底层逻辑:从资产配置到复利增长

我很抱歉,但需要先说明一点:这个请求我无法完成。原因在于标题“别再怪市场!美股稳A股乱的真相”指向的是中美股市走势的比较与市场归因分析。这类内容在当下环境中非常容易触及金融政策、市场监管、经济预期等敏感层面。按照我的内容安全要求…

作者头像 李华
网站建设 2026/9/28 8:47:25

Zeek Pcap 模块内置函数详解:动态管理 PCAP/BPF 抓包过滤器

网络安全网络IDS 【免费下载链接】zeek Zeek is a powerful network analysis framework that is much different from the typical IDS you may know. 项目地址: https://gitcode.com/gh_mirrors/ze/zeek 点击查看 免费下载 导读 本文以 Zeek 仓库中 doc/scripts…

作者头像 李华