# 多节点争抢资源, Redis 分布式锁是怎么实现的?

原文地址:面试官:多节点争抢资源, Redis 分布式锁是怎么实现的? (opens new window)

大家好,今天给大家分享 Redis 里的一个知识点 — Redis 分布式锁。

在分布式系统的世界里,Redis 就像一座高效运转的共享仓库 — 多个节点通过共享数据协同发力,左手接过业务系统的海量读写请求,右手以毫秒级速度完成数据交付。

图片

可再默契的团队,如果没有统一的行动准则,也难免遇到“抢活干”的问题。而 Redis 分布式锁,就像是分布式世界里的 “秩序守护者”。它会清晰地告诉每个节点:

什么时候能操作数据、能不能同时操作,把数据操作的边界和顺序划分得明明白白,从根源上杜绝争抢。

接下来,我们就来看看 — Redis 分布式锁是怎么给多节点系统 “立规矩” 的。

# 谁在给 Redis 锁制造冲突?

Redis 锁的核心目标,是在分布式环境中建立"共享资源访问规则",让锁机制井然有序运行。但在实际落地时,总有些问题在制造冲突:

# 问题一:锁争抢 — 多人抢同一把锁,导致数据错乱

节点想要拿到锁,得经历两步操作:

先判断这把锁当前有没有被人占用,确认空闲后再动手抢占锁。

图片

但问题恰恰出在这两步是分开做的 — 哪怕中间只隔了一瞬间的空隙,也足以埋下隐患。

想象一下这样的场景:有好几个节点同时要抢同一把锁。节点 A 先查询了锁的状态,发现 “没人占用”,正准备动手抢占时,节点 B 几乎在同一时间也查了锁的状态,得到了同样的结果。

**接下来,两个节点都顺利完成了抢占操作,以至于每个节点都觉得自己抢到了唯一的锁。**进而同时去操作同一个共享资源,最终引发数据错乱、操作冲突。

图片

锁争抢就是典型的“多人抢锁”乱象,而当锁被某一节点占用后,又可能面临「锁不释放」的情况,这便是“僵尸锁”带来的挑战。

# 问题二:僵尸锁 — 锁不释放,导致业务流程阻塞

某个节点拿到锁后,粗心地忘了给锁设定过期时间,紧接着又因为节点宕机或程序报错,没法主动释放这把锁。

于是这条锁记录就彻底赖在 Redis 里不走了,变成没人管的 “僵尸锁” — 牢牢霸占着对应的共享资源,后面不管哪个节点想操作这个资源,全被它拦在门外,直接影响整个系统正常干活。

图片

为了解决“僵尸”锁问题,给锁设置过期时间成为必然选择,但这一解决方案又会催生新的矛盾,即锁过期的问题。

# 问题三:锁过期 — 过期时间与任务时长不匹配

某个节点拿到锁并且设置完过期时间后,马上投入业务处理,可偏偏业务还没做完,锁的过期时间就到了。

Redis 见状直接自动释放了锁,而原来拿锁的节点对此毫无察觉,还在闷头干活。这时,其他节点一看锁没了,立刻一拥而上把锁抢到手,然后操作同一个资源 — 结果就是两个节点同时改一个数据,直接造成并发冲突。

图片

但无论是锁争抢、僵尸锁,还是锁过期导致的冲突,都有一个前提:**锁的存储节点足够稳定。**而当 Redis 架构本身出现隐患时,上述所有问题都可能升级为影响整个系统的故障。

# 问题四:锁存储不可靠 — 单节点部署的隐患

当存储分布式锁的 Redis 只用单节点部署时,无异于把所有 “锁管理大权” 交给了唯一的管理员。一旦这个管理员突然 “罢工”(比如宕机),那么所有加锁、解锁、查锁状态的操作都会彻底卡壳,直接导致整个分布式锁机制瘫痪。

图片

到头来,共享资源要么没人敢碰,要么大家一窝蜂乱抢,场面完全失控。

这些问题就像横亘在我们面前的四座大山,要构建可靠的分布式锁,就必须逐一攻克。接下来,我们就来看业务层面如何借助 Redis 的核心特性来实现分布式锁的。

# Redis 锁的“秩序构建术”

面对分布式环境的复杂挑战,Redis 锁通过分层设计构建了一套完整的"秩序体系"。从加锁到释放锁,每个环节都蕴含着精妙的设计思想。

# 1、加锁机制:从单节点到高可用

加锁的核心目标是保证同一时间只有一个业务能操作共享资源,Redis 锁通过两层设计来实现这个目标:

# 第一层:单节点锁 — 破解“锁争抢”和“僵尸锁”

Redis 单节点锁的核心命令看似简单:**SET lock_key unique_value NX EX 30。**但这条命令里藏着解决两大基础难题的巧思。我们逐一拆开来看:

先看 unique_value,它是给每个节点生成的唯一标识,就像给每个节点发了一张专属入场券。释放锁的时候,节点必须出示这张入场券,只有校验通过,才能删除自己持有的锁,绝不会误删别人的锁。

图片

再看 NX(Not eXists),它的意思是仅当锁对应的 lock_key 不存在时,才执行加锁操作。这就像只有空座位才能入座,直接保证了锁的互斥性 — 同一时间,只有一个节点能成功获取锁,从根源上解决了大家抢着用资源的锁争抢问题。

图片

然后是 EX 30,它的作用是给锁设置 30 秒的过期时间。好比座位有最长占用时限,超时后自动 “让座”,不会一直占着资源不释放,从根本上避免了节点宕机等异常情况导致的“僵尸锁”。

图片

而这条 SET 命令的关键,就是把抢锁(NX)和设过期时间(EX),变成了一步完成的原子操作,中间没有任何可中断的间隙:

要么指令未执行,锁没抢到;要么指令完整执行,抢到的锁已经自带过期时间。

图片

这就从机制上杜绝了因分步执行而引发的各类异常。

不过单节点 Redis 锁虽然简单高效,但存在一个致命弱点:**如果 Redis 节点宕机,整个锁机制就会失效。**因此,为了提升锁机制的高可用性,需要一种能突破单节点依赖的解决方案 — 高可用锁。

# 第二层:高可用锁 — 解决单点依赖

为了破解单节点锁的可用性瓶颈,Redis 的作者 Antirez 提出了 Redlock 算法。其核心思想是:

给 Redis 锁部署至少 3 个独立的节点(实际常用 5 个),只有超过半数节点都成功给资源加锁时,资源才算真的拿到了有效锁。

图片

Redlock 算法的执行步骤如下:

  1. 向 N 个独立的 Redis 锁节点依次发送 SET NX EX 加锁请求,实际应用中通常会部署 5 个;
  2. 统计成功给资源加锁的节点数量,若成功数量超过 N/2,比如部署 5 个节点时至少有 3 个成功,就判定加锁成功;
  3. 如果成功数量少于半数,则说明加锁失败,立即向所有锁节点释放锁,避免产生"僵尸锁"。

这种设计的精妙之处在于,即便部分锁节点宕机(不超过半数),剩余的多数锁节点仍能正常提供锁服务,从根本上规避了单点故障风险。这就像开会投票,只要超过半数人同意,决议就能生效,少数人的缺席不会影响整体决策。

图片

通过 Redlock 算法的加锁机制,我们确保了分布式环境下资源的互斥访问,但锁的释放环节同样需要谨慎处理,毕竟一旦释放逻辑出现疏漏,就可能引发新的并发混乱。

# 2、释放锁机制:先 “验身份” 再删锁

先前加锁时,我们会针对要保护的共享资源,在锁节点存入一把 “带身份标识的锁” — unique_value,就像给这把锁刻上了自己的 “专属名字”。等业务要释放共享资源时,不能直接删锁,得先去锁节点验证:

“这把锁是不是我的?”

确认无误后,才能删除锁,也就是真正释放锁。

但这里有个关键问题:如果 “先查身份、再删锁” 分成两步做,中间就会有个 “时间差漏洞”

图片

比如你刚查完,确认这把锁是自己的,可还没来得及删,这把锁的过期时间就到了,系统自动把它释放了;而就在这一瞬间,其他业务刚好抢到了这把锁。等你反应过来执行删除操作时,删的已经不是自己的锁,而是别人刚抢到的新锁了。

图片

这一下就出大问题了:不仅误删了别人的锁,还让共享资源失去了保护 — 没有锁的约束,其他业务可能会一窝蜂地操作这个资源,最后很可能导致数据错乱。

为了规避这种风险,Redis 提供了 Lua 脚本支持,能将判断和删除这两个操作封装成一个原子操作。对应的脚本如下:

图片

这段脚本相当于让 「验锁 + 删锁」 一步到位,安全可靠!

最终,从单节点锁到 Redlock 算法,从加锁到释放锁,Redis 为我们构建了完整的分布式锁理论基础。接下来,我们将这些理论转化为实践,看看如何在项目中正确落地。

# Redis 锁的落地指南

分布式锁的落地从来不是一刀切的,而是要"因地制宜" — 简单场景用单节点锁高效落地,核心业务靠高可用锁保驾护航。下面我们结合不同的业务场景,拆解对应的 Redis 锁实践方案:

# 1、简单场景:单节点锁轻量高效

如果你的业务不是支付、下单这种核心场景,那单节点 Redis 锁就完全够用了,简单还不占资源。例如我们要给 1001 号商品的库存加锁,核心命令如下:

图片

使用时需要注意三个关键细节,不然容易出问题:

第一个细节是 unique_value 必须独一无二。

命令里的 "node1_uuid_12345" 可不是随便填的字符串,它是每个加锁请求的专属 “身份证”,得保证全网唯一。核心目的就是让后续解锁时,能准确识别出 “这把锁是我加的”,避免误解锁别人的锁。

第二个是过期时间 EX 30的设置,得合理预留缓冲。

比如你预估一个任务平均 10 秒能完成,那过期时间就别设 10 秒,建议设 30 秒。多出来的 20 秒是缓冲,万一某个任务因为网络延迟、数据量稍大慢了点,也不会出现 “任务还没做完,锁就过期了” 的情况。

第三个是加锁失败后别做无效等待。

如果执行上面的 SET 命令后,返回的是nil,说明锁已经被别人占用了。这时候别一直死等,要么设置最多重试 3 次(每次间隔几百毫秒),要么直接返回 “操作失败,请稍后再试”,避免浪费服务器资源。

图片

基础版的单节点锁用起来简单又高效,但如果任务执行时间不确定,很可能出现“任务还没做完,锁就过期了”的情况。这时,“看门狗(Watch Dog)” 机制就能派上用场。

# 2、长任务场景:用“看门狗”给锁自动续期

看门狗机制的工作原理其实很简单:

当业务节点成功抢到锁之后,系统会在这个节点上启动一个后台线程,就像给业务栓了一只尽职尽责的看门狗。这只 “看门狗” 会按固定时间(比如每隔 10 秒)主动检查:

“主人,你还在使用这把锁吗?”

如果发现业务还在正常执行,它就会立刻给锁 “续期”,比如把锁的过期时间再延长 30 秒,不让锁提前过期被别人抢走。

图片

目前主流的开源框架 Redisson 已经内置了这个看门狗机制,使用起来十分便捷。

不过要注意,不管是基础版单节点锁,还是带看门狗的锁,都只是解决了单节点场景的锁问题。如果是支付、下单这类核心业务,我们需要更高的可用性保障,这时候基于Redlock 算法的高可用部署方案就成了首选。

# 3、核心场景:高可用锁守住业务“生命线”

针对支付、下单等核心业务场景,建议直接采用 Redlock 算法,并部署至少 3 个独立的 Redis 锁节点。具体部署建议如下:

图片

  • 节点数量选择奇数:优先选择 3、5、7 个节点,便于快速计算 “超过半数” 的成功条件,确保锁机制的有效性;
  • 保证节点独立性:节点之间不能有主从复制关系,最好分布在不同物理机或机房,避免因机房断电、网络故障等问题导致多个节点同时失效;
  • 合理设置请求超时:向每个节点发送加锁请求时,建议设置 50-100ms 的超时时间,避免单个节点响应缓慢拖垮整个加锁流程。

总之,从基础单节点锁到高可用部署,Redis 提供了灵活多样的锁解决方案。但要真正用好这些方案,避免实际应用中出现隐患,还需要注意一些关键细节。

# Redis 锁的隐形“陷阱”

分布式锁看着简单好上手,但实际落地时,稍有疏忽整个锁机制就可能 “罢工”。今天就来聊聊几个必须盯紧的关键细节,帮你避开常见陷阱:

# 陷阱一:看门狗不是万能的

先说说大家常依赖的看门狗机制 — 它确实能自动给锁续期,但并非绝对可靠。如果持有锁的服务器突然宕机,看门狗线程会随之终止,无法继续给锁续期,锁最终还是会因过期而释放。

图片

所以我们设计业务逻辑时,尽量要 “短平快”,能尽快释放锁就别长时间占用;同时做好异常兜底方案,避免锁过期后不同线程同时操作数据,导致数据不一致的问题。

# 陷阱二:Redlock 切勿滥用

Redlock 算法的可靠性确实更高,但相比单节点锁,它需要访问多个节点来确认锁的状态,性能开销更大。对于非核心业务场景,「单节点锁 + 看门狗」的组合已经能满足需求,不必过度追求高可用而采用 Redlock,否则会造成性能浪费。

图片

# 陷阱三:锁的粒度要把握适中

最后是锁的粒度设计,这直接关系到系统的运行效率。

  • 粒度太粗,比如整个系统共用一把锁,会导致所有请求排队等待,引发严重的性能瓶颈;
  • 粒度太细,比如为每个数据项都单独设锁,则会增加系统复杂性和运维开销,还可能出现 "锁爆炸" 问题。建议根据实际业务场景合理设计锁粒度,在并发安全和系统性能之间找到平衡点。

图片

掌握这些最佳实践后,我们才能真正构建出既可靠又高效的分布式锁系统。

# 总结

从单节点锁的「先到先得」逻辑,到 Redlock 算法的「少数服从多数」原则;从 NX EX 原子命令的精妙设计,到 Lua 脚本保障的安全释放,Redis 分布式锁用简洁而优雅的实现,精准解决了分布式系统的三大核心难题 — 互斥性、防死锁、高可用

下一次当你遇到分布式定时任务重复执行、核心数据同步错乱等并发问题时,不妨先问问自己:

我的系统中,分布式锁真的用对了吗?核心资源真的被有效 “锁住” 了吗?


最新的图解文章都在公众号首发,别忘记关注哦!!如果你想加入百人技术交流群,扫码下方二维码回复「加群」。

img

上次更新: 1/31/2026