用Redis搞个阻塞分布式锁,感觉比想象中复杂还好玩儿
- 问答
- 2026-01-08 08:43:24
- 4
(引用来源:知乎问题“用Redis搞个阻塞分布式锁,感觉比想象中复杂还好玩儿”下的高赞回答,作者“小灰”)
用Redis搞个阻塞分布式锁,感觉比想象中复杂还好玩儿,一开始你以为不就是个锁嘛,SET一个key,完了DEL掉,多简单,但真动手做,才发现坑是一个接一个,每个坑都挺有意思,像在解谜。
第一个大坑:原子性
最天真的想法是,客户端A执行SET lock_key client_id,如果返回OK,说明抢到锁了,用完后再DEL lock_key释放锁,听着没问题对吧?但万一客户端A在SET之后、DEL之前,程序崩溃了怎么办?这个锁就永远没人能释放了,成了“死锁”,其他客户端全都得干等着。
得给锁加个“过期时间”,想法升级为:SET lock_key client_id EX 10,10秒后自动过期,这样即使客户端挂了,锁也能自己解开,但这里又有个小坑:SET和设置过期时间必须是原子操作,你不能先SET,再EXPIRE,因为万一SET成功,但还没执行EXPIRE客户端就崩了,又死锁了,幸好Redis提供了把SET和过期时间写在一起的命令,一步到位,解决了这个问题。
第二个大坑:释放锁的权限
锁加了过期时间,安心了不少,现在客户端A抢到锁,执行操作,然后准备DEL key释放锁,但你想过没有,万一客户端A因为某些原因(比如垃圾回收)卡住了,超过了10秒,锁自动释放了,此时客户端B成功抢到锁,然后客户端A缓过劲儿来了,它还以为锁是自己的,顺手就是一个DEL……完蛋,客户端B的锁被A给删了!客户端C一看锁没了,也冲进来,系统就乱套了。
锁不能乱删,你得确保“谁加的锁,谁才能删”,解决办法就是在SET的时候,value不要用简单的1或true,而是用一个唯一标识,比如UUID或者客户端ID,释放锁的时候,先GET lock_key看看value是不是自己当初设置的那个,如果是,才执行DEL。
但这里又又又有个坑!GET和DEL是两个操作,不是原子的,你GET发现value是自己的,正准备DEL,这时候锁过期了,而且被客户端B抢去了(value已经是B的了),你紧接着的DEL操作又会把B的锁误删掉。

判断value和删除这个动作,也必须是原子的,这时候就需要用到Lua脚本了,Redis支持用Lua脚本一次性执行多条命令,保证原子性,脚本大概长这样:if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end,这样才安全地实现了“只能删自己的锁”。
第三个大坑,也是最玩儿的地方:阻塞
上面说的,其实只是个“非阻塞锁”,如果锁被占用,其他客户端只能失败返回,或者不停地重试(这叫“忙等待”),很浪费资源,我们想要的是“阻塞锁”:拿不到锁就在那里等着,一旦锁被释放,能立刻被唤醒去抢。
怎么实现“等”和“唤醒”呢?最简单的想法是让客户端隔一秒检查一次(轮询),但这样有延迟,也不够优雅。
更“Redis”的做法是利用其“发布订阅”功能,思路是这样的:

- 所有没抢到锁的客户端,都
SUBSCRIBE(订阅)一个特定的频道,比如lock_channel。 - 当某个客户端释放锁的时候,它在删除锁之后,再向
lock_channel``PUBLISH(发布)一条消息,锁释放啦!” - 所有订阅了这个频道的客户端都会收到消息,它们一收到消息,就立刻跳起来再去尝试抢锁。
但这里有个非常精妙的竞争条件,特别好玩:客户端A释放锁,DEL锁和PUBLISH消息这两个动作,即便用Lua脚本保证原子性,也没法完美解决,想象一下这个顺序:
- 客户端A执行删除锁的Lua脚本,锁被释放。
- 一直在轮询的客户端B瞬间检测到锁没了,成功
SET,抢到了锁。 - 紧接着,客户端A的
PUBLISH消息才发出去。 - 客户端C收到消息,也去抢锁,但此时锁已经被B持有了,C只能继续等待。
你看,B通过轮询“偷跑”成功了,而C虽然收到了通知,却还是没抢到,这其实没问题,因为我们的目标是保证同一时间只有一个客户端能拿到锁,B和C谁抢到都行,但这样会导致C白白激动一次,更高效的做法是,收到通知的客户端在抢锁前,再检查一下锁是否确实已经被释放(也就是还是用轮询的思想,但只在收到通知后才轮询一次),避免无效的争抢。
感觉比想象中复杂还好玩儿
你看,就这么一个锁,从最简单的SET/DEL,到加过期时间防止死锁,到用唯一value防止误删,再到用Lua脚本保证原子操作,最后用发布订阅实现阻塞通知,每一步都是为了解决一个实际场景中必然会出现的坑。
这个过程就像搭积木,或者解决一个逻辑谜题,你以为解决了,马上就有新的边界情况冒出来挑战你,当你用Lua脚本、发布订阅这些基础积木,最终搭出一个健壮的分布式锁时,那种成就感是非常足的,它让你深刻地理解了分布式系统里“时间”和“顺序”的微妙性,也感受到了Redis这种简单工具所能构建出的强大能力。
所以有人说,你能亲手实现一个正确的Redis分布式锁,就算是对分布式并发问题入门了,这话一点也不假,它看似简单,却内涵乾坤,确实又复杂又好玩儿。
本文由水靖荷于2026-01-08发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/76715.html
