Redis分布式锁怎么搞,聊聊实现原理和常见方法探讨
- 问答
- 2026-01-21 17:28:33
- 6
你有一个程序,部署在了三台机器上,现在有一个任务,给用户发一张优惠券”或者“扣减某个商品的库存”,这个任务在同一时间只能被一台机器上的一个程序执行一次,不然就会乱套,比如一张优惠券被发了两次,或者库存被多扣了,这个时候,你就需要一个“锁”来保证互斥性,单机程序用线程锁就行,但现在你的程序跑在多台机器上,这个锁就必须是“分布式”的,也就是所有机器都能访问和认可的一个东西。
Redis因为它速度快、单线程处理命令的特性(这很关键,避免了并发问题),自然就成了实现这种分布式锁的热门选择,它的核心思想很简单:就是大家来抢一个“钥匙”,谁先在Redis里创建了这个钥匙,谁就获得了锁,就可以去执行任务。
(来源:Redis官方文档中关于分布式锁的论述,常被称为“SETNX”方法)
最古老也是最直接的方法,就是用Redis的两个命令:SETNX和DEL。
SETNX的意思是“SET if Not eXists”,如果键不存在,它就设置键值对,并返回1表示成功;如果键已经存在,就不做任何操作,返回0表示失败。- 加锁的过程就是:机器A执行
SETNX lock_key 1,如果返回1,恭喜你,加锁成功,赶紧去干活,干完活后,为了释放锁让别人能用,机器A再执行DEL lock_key把钥匙删掉。 - 这时,如果机器B也尝试执行
SETNX lock_key 1,就会返回0,加锁失败,它就得等着(比如过一会儿再试),或者直接放弃。
这个方法有个致命问题:死锁,如果机器A加锁成功后,在释放锁之前突然崩溃了,那么这把锁就永远留在Redis里了,其他机器再也无法获得锁,系统就“死”了,为了解决这个问题,我们得给锁加一个“过期时间”。

(来源:同样是Redis官方文档的改进方案)
改进后的方法是使用 SET 命令的一个参数组合:SET lock_key random_value NX PX 30000。
NX还是表示“如果不存在才设置”,保证了互斥性。PX 30000表示给这个键设置一个30000毫秒(30秒)的过期时间,这样,即使获得锁的客户端崩溃了,最多30秒后,Redis也会自动删除这把锁,避免了死锁。- 这里还有一个关键点:
random_value(随机值),为什么值不简单地设为1,而要一个随机值(通常是UUID)呢?这是为了确保锁是由加锁者自己来释放的,考虑这个场景:机器A加锁成功,开始执行任务,这个任务执行了比较久,超过了30秒,锁被Redis自动释放了,这时机器B获得了锁,紧接着,机器A的任务执行完了,它去执行DEL lock_key,结果把机器B刚创建的锁给删掉了!这就会导致混乱,正确的做法是,每个客户端用自己唯一的随机值作为锁的值,在释放锁的时候,要先判断一下当前锁的值是不是自己设置的那个随机值,如果是,才能删除,这个判断和删除操作必须是原子的,所以要用Lua脚本来实现:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
这套方案在大多数情况下已经够用了,但它还不是完美的,它存在一个问题,我们称之为“时钟漂移”问题,锁的过期时间是10秒,但客户端1可能因为某种原因(如GC垃圾回收)卡顿了15秒,它以为自己还持有锁,但实际上锁已经过期并被客户端2获取了,这时就可能出现两个客户端同时进入临界区的情况。

(来源:Martin Kleppmann的论文《How to do distributed locking》)
为了解决更复杂的可靠性问题,Redis的作者Salvatore Sanfilippo提出了一个更严格的算法,叫做Redlock,这个算法适用于在Redis是真正分布式(多个主节点,且彼此独立)的场景下,追求更高的一致性。
Redlock的基本思路是,你不只在一台Redis实例上创建锁,而是在一个由多个(比如5个)独立的Redis主节点组成的集群上依次尝试创建锁,它的步骤是:
- 获取当前时间戳。
- 依次向5个Redis实例发送加锁命令(就是上面带NX、PX和随机值的SET命令)。
- 当客户端从大多数(N/2 + 1,这里是3台)Redis实例上成功获取锁时,并且总耗时小于锁的有效时间,才算加锁成功。
- 如果加锁失败(比如成功数没达到3个,或者总耗时超过了锁的有效期),那么客户端要向所有Redis实例发起释放锁的请求(用那个Lua脚本)。
- 加锁成功后,锁的真正有效时间等于最初设置的有效时间减去加锁过程的总耗时。
Redlock通过引入多节点投票机制,降低了单点Redis故障带来的风险,但它也更重、更复杂,而且它是否能提供绝对的可靠性在学术界还有争议(就是Martin Kleppmann和Redis作者之间那场著名的辩论),通常的建议是:如果你的业务场景可以接受偶尔的锁失效(比如为了性能做秒杀,偶尔超卖一两个也能接受),那么单Redis实例的锁就足够了;如果你需要万无一失的严格锁,那可能需要考虑ZooKeeper等更强调一致性的系统,而不是Redlock。
用Redis搞分布式锁,核心就是“占坑+过期时间+所有者验证”,从简单的SETNX/DEL,到带过期时间和随机值的SET命令配合Lua脚本,再到复杂的Redlock算法,都是在不同程度上为了平衡性能、可用性和一致性,选择哪种方法,完全取决于你的具体业务场景和容忍度。
本文由凤伟才于2026-01-21发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/84097.html
