Redis锁怎么用来抢占并发访问权限,简单聊聊它的操作和注意点
- 问答
- 2026-01-04 13:30:40
- 18
一张特价电影票,好几个人同时点击购买,如果不加控制,数据库里可能就会卖出去好几张票,但实际上票只有一张,这时候就需要一个“锁”的机制,保证在同一时间,只有一个人能执行买票这个操作,Redis因为速度非常快,经常被用来实现这种锁。
核心操作:最简单的加锁与解锁
最基础的Redis锁,就用两个命令:SETNX 和 DEL。
-
加锁(抢锁):
SETNX是“SET if Not eXists”的缩写,意思是,只有当这个键不存在的时候,我才能设置成功,我们可以把锁想象成一把钥匙,这把钥匙对应Redis里的一个唯一的键(Key),lock:order:123(123是订单ID)。- 第一个用户A来了,执行
SETNX lock:order:123 1,因为此时这个键不存在,所以Redis返回1,表示设置成功,用户A就认为他抢到了锁,可以安心地去执行后续的扣减库存、生成订单等敏感操作了。 - 紧接着,用户B也来了,他也执行
SETNX lock:order:123 1,但此时这个键已经被A创建了,所以Redis返回0,表示设置失败,用户B就知道锁已经被别人占用了,他可以选择放弃或者过一会儿再来试试。
- 第一个用户A来了,执行
-
解锁(释放锁):当用户A处理完所有业务逻辑后,他必须主动把锁释放掉,这样其他等待的人才能抢到,释放锁很简单,就是直接把这个键删掉:
DEL lock:order:123。
第一个大问题:死锁
上面这个最简单的模型有个致命问题:如果用户A在抢到锁之后,还没来得及执行DEL命令释放锁,他的程序突然崩溃了,或者服务器宕机了,那会怎样?这把锁就会永远留在Redis里,其他所有用户再来执行SETNX都会失败,这个订单就再也没人能买得到了,这就是“死锁”。
解决死锁:给锁加个“保质期”
为了解决死锁,我们引入了一个新的命令:SET 命令的扩展参数,现在更推荐直接用 SET key value EX seconds NX 来一步到位地加锁。
NX还是表示“如果不存在才设置”,保证了互斥性。EX后面跟一个秒数,EX 10,表示给这个键设置一个10秒后自动过期的超时时间。
这样,即使用户A的程序崩溃,最多等待10秒钟,Redis也会自动把锁删除,其他用户就可以重新抢锁了,这个超时时间就是我们给锁加的一个“保险”。

第二个大问题:误删别人的锁
加了超时时间,死锁问题解决了,但新的问题又来了,假设用户A抢到了锁,设置的超时时间是10秒,但是他的业务逻辑比较复杂,执行了15秒才完成,在第10秒的时候,锁因为超时被Redis自动释放了,此时用户B成功抢到了锁,第15秒的时候,用户A的业务逻辑执行完了,他依然去执行了 DEL 命令,这下坏了,他把用户B刚抢到的锁给删掉了!
这就好比你的储物柜超时被管理员打开,别人已经放进东西了,你却用自己的旧钥匙(虽然已经失效但删除操作依然能执行)把柜门打开并把别人的东西清空了。
解决误删:锁的值要有唯一性
解决办法是,我们在设置锁的时候,不能简单地设值为1,而应该设为一个唯一的值,比如一个随机生成的字符串或者用户的UUID,在删除锁的时候,要先判断一下当前锁的值是不是自己当初设置的那个值,如果是,才能删除。
这个“判断值+删除”的操作必须是原子性的(即要么都成功,要么都失败,不能被打断),因为如果判断完值相等后,锁突然过期又被别人抢走,你还是会误删,Redis本身没有直接提供这种命令,但我们可以用Lua脚本来实现,Lua脚本在Redis中是原子执行的。

流程就变成了:
- 加锁:
SET lock:order:123 随机字符串 EX 10 NX - 执行业务逻辑。
- 解锁:执行一段Lua脚本,内容大概是:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end调用时传入键名
lock:order:123和当初设置的“随机字符串”,这样就能确保只会删除自己持有的锁。
其他需要注意的点
-
超时时间设置多长? 这是个难题,设短了,容易在业务没完成时锁就超时,导致并发问题(比如库存扣减了两次),设长了,万一持有锁的客户端真的挂了,其他客户端要等待很久才能继续,通常需要根据业务逻辑的平均耗时和峰值耗时来评估,并留出充足的余量。
-
自旋锁(重试机制):当一个客户端抢锁失败时,它不会直接放弃,而是等待一小段时间(sleep 100毫秒)后再次尝试抢锁,这种循环重试的模式就叫自旋,但要注意,如果很多客户端都在自旋,会对Redis造成压力。
-
Redis主从切换的风险:如果你用的Redis是主从模式,锁数据会异步地从主节点复制到从节点,如果主节点挂了,虽然从节点会切换成新的主节点,但有可能锁数据还没来得及复制过去就丢失了,这会导致另一个客户端也能在新的主节点上成功加锁,出现两个客户端同时持有锁的情况,如果业务场景对这一点要求极其严格,就需要使用Redis的Redlock算法(一种分布式锁算法,需要同时向多个独立的Redis实例申请锁)来实现更高级的分布式锁,但这会复杂得多。
用Redis实现一个简单可用的锁,核心就是:用 SET NX EX 命令加锁并设置超时避免死锁,用判断唯一值再删除的Lua脚本解锁避免误删,同时要清醒地认识到超时时间设置的权衡以及在高可用架构下可能存在的风险。
本文由芮以莲于2026-01-04发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/74349.html
