Redis怎么防止脏读和死锁,锁机制其实挺关键的,聊聊它是咋解决这些问题的
- 问答
- 2026-01-05 11:03:51
- 12
首先得弄明白什么是脏读和死锁,不然没法往下说,脏读,简单打个比方,就是你看到的数据是“不干净”的,你和朋友同时看银行账户余额,都是100元,然后你取走了50元,正在操作但还没最终确认的时候,你朋友也来取款,他看到的可能还是100元(脏数据),如果他基于这100元也取了50元,那银行就亏大了,这就是典型的脏读引发的数据错乱。
而死锁,就像两个人过独木桥,迎面走来,都卡在桥中间,每个人都要求对方先退回去让自己过,但谁也不让谁,结果就是大家都僵在那里,谁也动不了,在程序里,就是两个或多个操作互相等待对方释放锁,导致程序卡死。
Redis本身是一个单线程处理命令的内存数据库,这个特性帮它避免了很多麻烦,对于脏读,Redis有天然的“防御机制”,因为所有命令都是排着队一个一个执行的,绝对不会发生一个命令执行到一半,另一个命令插进来读到一个中间状态的数据,就像银行柜台只有一个窗口,业务必须办完一个再办下一个,后面的人看到的永远是前一个人办完后的最终结果,在单个Redis命令的执行层面,你不用担心脏读。
现实中的业务往往不是一条命令就能搞定的,比如那个经典的“秒杀”场景:先检查库存是否大于零,如果大于零,再减少库存,这需要两条命令,如果不用锁,成百上千的请求同时涌进来,可能所有请求在第一步检查库存时都看到还有货,然后都去执行减库存,结果库存就变成负数了,这显然不行,这时候,就需要引入锁机制来把这一系列操作变成一个不可分割的“原子操作”。
Redis最常用的锁就是SETNX命令(SET if Not eXists),它的逻辑很简单:我去设置一个键,如果这个键不存在,我就设置成功,相当于我拿到了锁;如果这个键已经存在,说明锁被别人拿走了,我就设置失败,得等着,拿到锁的人操作完后,再把这个键删掉(释放锁),其他人才能有机会拿到。
这个简单的锁会碰到几个大问题,搞不好就会导致死锁或者数据错误:
- 死锁风险一:锁被永久持有,如果某个客户端拿到锁之后,还没来得及释放锁,自己就崩溃了或者网络断开了,那么这个锁就永远没人能删掉了,其他所有客户端都会永远拿不到锁,系统就瘫痪了,这就是个典型的死锁场景。
- 解决之道:给锁加个“保质期”,这就是Redis官方后来推荐的用法:使用
SET key value NX PX milliseconds命令,这条命令能原子性地完成“设置键(如果不存在)”和“设置过期时间”两个动作。SET lock:order123 userId1 NX PX 10000,意思是尝试获取锁lock:order123,如果成功,这个锁会在10秒后自动过期,这样,即使客户端崩溃,锁也会在10秒后自动释放,避免了永久死锁,这个方法是Redis作者Antirez在博客中多次强调的正确做法。 - 死锁风险二:误删别人的锁,假设客户端A拿到锁,设置了10秒超时,但A的业务逻辑比较复杂,干了15秒还没干完,这时候锁因为到期自动释放了,客户端B趁机拿到了锁,紧接着,A终于干完活了,它去执行删除锁的操作,结果就是把B刚创建的锁给删掉了!这会导致锁机制完全失效。
- 解决之道:给锁配上“指纹”,我们在设置锁的时候,value不要用简单的1或true,而是用一个唯一的值,比如UUID或者客户端ID,在删除锁之前,先检查一下这个锁的value是不是自己当初设置的那个,如果是,才能删;如果不是,说明锁已经不属于自己了,就不能删。“检查value”和“删除锁”这是两个操作,不是原子的,中间可能被打断,我们需要用Lua脚本来保证这两个操作的原子性,Redis支持Lua脚本,可以确保脚本里的命令被一次性、按顺序执行,不会被其他命令插入。
一个相对完善的Redis锁流程是这样的:
- 加锁:
SET lock_key unique_value NX PX 30000(用唯一值加锁,并设置30秒过期) - 业务操作:执行需要受保护的代码。
- 解锁:通过Lua脚本执行
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end(先核对指纹,再删除)。
通过这套组合拳——SET NX PX命令避免永久死锁 + Lua脚本核对value避免误删——Redis就能构建出一个在分布式环境下比较安全的互斥锁,这个锁机制通常被称为Redlock算法思想的基础,不过Redlock是更复杂、用于跨多个Redis实例的场景,其基本精神是一致的:通过设置超时防止死锁,通过唯一标识保证锁的所有权。
Redis利用其单线程特性天然避免了单命令内的脏读,而对于复杂的业务逻辑,则通过精心设计的分布式锁(设置过期时间和使用唯一值验证)来将一系列操作“串行化”,从而防止并发下的脏读,同时通过锁的自动超时机制来有效避免死锁的发生。

本文由雪和泽于2026-01-05发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/74911.html
