学Redis锁那些事儿,搞懂了应用才更溜,锁技术真心不能忽视
- 问答
- 2026-01-03 22:13:30
- 8
微信公众号“程序员小灰”及相关技术社区讨论的综合整理)
咱们今天就来聊聊Redis锁这个事儿,你可能觉得,锁嘛,不就是让多个线程或者多个服务别同时碰一个东西,听起来挺简单的,但在实际开发中,尤其是在现在动不动就分布式、微服务的环境下,用Redis实现一个靠谱的锁,里头的门道可多了去了,搞不好就会掉坑里。
先说说为啥要用Redis做锁。
想象一个场景:你有一个电商系统,搞秒杀活动,一件热门商品库存就100件,但同时有上万个人在疯狂点击“立即购买”,如果你的系统部署了好几个服务实例(也就是分布式部署),每个实例都去处理请求,然后都去数据库里查“还剩多少库存”,一看,哎,还有货,于是都告诉数据库“减一”,这一下,可能100件商品被卖出了200件,这就叫“超卖”,是严重的事故,这时候你就需要一把“锁”,在某个服务实例处理这个商品的购买请求时,把这件商品“锁”起来,其他实例想来操作,得等着,等前一个处理完了(锁释放了),才能进来,因为Redis本身特别快,而且是单线程处理命令(避免了多线程的并发问题),天生就适合用来做这种分布式锁的中心节点。
最简单的Redis锁是咋样的?
最直接的想法就是用Redis的一个命令:SETNX,这个命令的意思是“SET if Not eXists”(如果不存在才设置),我给秒杀商品A设一把锁,锁的键(Key)可以叫 "lock:sku_12345",值(Value)随便设个啥都行,第一个服务实例来了,执行 SETNX lock:sku_12345 1,Redis里本来没有这个键,所以设置成功,这个实例就拿到了锁,紧接着第二个实例也来执行同样的SETNX命令,但此时键已经存在了,所以设置失败,拿不到锁,它就只能等待或者直接返回“抢购失败”,等第一个实例处理完下单扣库存的逻辑后,再执行 DEL lock:sku_12345 把锁删掉,这样其他等待的实例就有机会再次通过SETNX来抢锁了。

听着挺完美?坑来了!
这第一个大坑就是“死锁”,万一第一个实例在拿到锁之后,还没来得及执行DEL命令删除锁,突然宕机了!那这把锁就永远留在Redis里了,lock:sku_12345这个键会一直存在,其他所有实例再也无法获得锁,这个商品就再也卖不出去了,整个系统就被这把“死锁”卡死了。
怎么解决死锁?给锁加个“保质期”。
很简单,我们给这个锁设置一个过期时间(TTL),比如10秒钟,就算拿到锁的实例宕机了,10秒钟后Redis也会自动把这个键删除,锁就释放了,在Redis里,我们可以用 SET key value EX seconds NX 这个组合命令,它能把设置值和设置过期时间变成一个原子操作(要么一起成功,要么一起失败),这就避免了刚SETNX成功还没来得及设置过期时间就宕机的问题,这就是我们常说的“SETNX + EXPIRE”的原子化实现。

又完美了?第二个坑等着呢!
假设实例A拿到了锁,设置的过期时间是10秒,但是A的业务逻辑有点复杂,可能因为数据库慢查询、调用其他服务网络延迟等等,处理了15秒才完成,可在第10秒的时候,锁因为过期自动释放了,这时实例B瞅准机会成功拿到了锁,过了5秒,也就是第15秒的时候,实例A终于处理完业务了,它浑然不知自己的锁早已失效,顺手就执行了DEL lock:sku_12345命令,坏事了!它删掉的不是自己的锁,而是实例B刚刚创建的新锁!这下实例C也能来抢锁了,可能同时有两个实例在操作共享资源,锁的安全性又被破坏了,这叫做“误删别人锁”的问题。
怎么解决误删?给锁加上“指纹”。
我们可以在设置锁的时候,把Value值设成一个唯一标识,比如UUID或者请求ID,实例A设置锁时,SET lock:sku_12345 uuid_A EX 10 NX,当它要删除锁的时候,不能直接删,要先GET一下这个锁的值,看看是不是自己当初设置的那个uuid_A,如果是,才能删除;如果不是,说明锁已经不属于自己了,就不能删。

GET和DEL是两个命令,不是原子的呀!
对,这又是个问题,如果实例A刚GET完,发现值匹配,正准备执行DEL的时候,锁突然过期了,然后实例B又设置了新锁,这时实例A的DEL命令还是会误删实例B的锁,判断锁值和删除锁的这个操作,也必须是原子的,幸运的是,Redis支持Lua脚本,我们可以写一小段Lua脚本,在Redis服务器端原子性地执行“判断值是否匹配,匹配则删除”的逻辑,这样就比较稳妥了。
还有更复杂的情况吗?有的,锁续期”。
对于那种业务执行时间不确定,但可能比较长的场景,我们希望在锁快要过期的时候,如果业务还没做完,能自动给锁“续命”,延长过期时间,这个机制就是“看门狗”(Watchdog)机制,一些成熟的Redis客户端(比如Redisson)就内置了这个功能,它在后台启动一个线程,定期检查业务是否还在执行,如果还在执行,就延长锁的过期时间。
总结一下
你看,从一个简单的SETNX命令开始,为了做出一个在生产环境下稳定可靠的分布式锁,我们得一步步解决死锁、误删、原子性、续期等一系列问题,这还只是单Redis实例的情况,如果考虑Redis主从架构下的故障转移,还有更复杂的红锁(RedLock)算法来应对,但那又是另一个更深的话题了。
所以我说,锁技术真心不能忽视,它看似基础,但细节决定成败,搞懂了这些门道,你在设计分布式系统、处理高并发场景时,心里才有底,应用起来才更溜,不至于等到线上出了诡异的数据错乱问题,才追悔莫及,平时多看看像“程序员小灰”这样能把复杂技术讲得通俗易懂的文章,多动手实践一下,绝对是提升技术功力的好方法。
本文由革姣丽于2026-01-03发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/73951.html