Redis分布式锁用来保证多进程多线程环境下,单个进程或线程任务不重复执行的那些事儿
- 问答
- 2026-01-24 08:01:38
- 2
主要综合了网络上关于Redis分布式锁的常见讨论和实践,特别是来自博客园、CSDN、开源中国等开发者社区的文章观点,以及Redis官方文档中对SET命令NX和PX参数的说明)
说起Redis分布式锁,其实就是为了解决一个很实际的问题:现在有很多台机器,或者一台机器上开了很多个进程,每个进程里又有很多个线程,它们可能要同时去执行同一个任务,有一个定时任务,是每天凌晨给所有用户发优惠券,如果你只部署了一台服务器,那没问题,一个定时任务跑起来就行了,但要是为了应对高并发,你部署了三台服务器,每台服务器上都部署了同样的代码,都有这个凌晨运行的定时任务,那么到了凌晨,三台机器就会同时启动这个任务,结果就是同一个用户可能会收到三张一模一样的优惠券,这显然是不对的。
再举一个例子,比如有一个商品库存只剩下一件了,这时候有两个用户在不同的手机上同时点击购买,这两个请求很可能被分发到不同的服务器上去处理,如果不加控制,两台服务器都去数据库里查了一下,发现库存是1,都觉得可以卖,然后都进行了减库存的操作,结果库存变成了负数,这就叫超卖了,分布式锁就是为了防止这类“重复执行”或者“超额执行”的问题。
它的核心思想很简单,占坑”思维,想象一下,有一个公共的厕所,只有一个坑位,大家想上厕所,就得看坑位门口有没有显示“有人”,这个“有人”的牌子,就是锁,在程序的世界里,这个“坑位”就是那个不能同时执行的任务或者要保护的资源(比如那个优惠券发放任务或者那个商品库存),而Redis,因为其速度快、本身是单线程处理命令(避免了并发问题)、以及支持网络访问,就很适合来当这个“厕所管理员”,负责管理这个“有人”的牌子。

具体怎么做呢?最基本的方式就是利用Redis的一个命令:SET key value NX PX,这里解释一下,NX的意思是“只有当这个key不存在的时候才设置它”,这就好比你去厕所,只有当“有人”牌子没挂的时候,你才能挂上牌子进去,PX是设置这个key的过期时间,单位是毫秒,这太重要了!这相当于你给上厕所加了个时间限制,比如10分钟,万一有人在里面晕倒了(比如持有锁的进程崩溃了,没有主动释放锁),10分钟后这个锁也会自动失效,别人就能进去了,避免了“死锁”的发生,也就是牌子永远挂着,谁也进不去了。
一个基本的加锁过程是这样的:一个进程要来执行任务前,先向Redis发起请求,尝试设置一个特定的key(比如lock:send_coupon),并给它一个随机值(比如UUID),同时设置一个过期时间(比如10秒),如果设置成功了,就说明它抢到了锁,然后它就去执行发放优惠券的任务,执行完毕后,它再主动发个命令给Redis,把这个key删掉,也就是释放锁,摘下“有人”的牌子。
那如果它没设置成功呢?说明这个key已经存在了,也就是锁已经被别的进程占用了,那这个进程就只能干等着,或者过一会儿再来试试,这个过程就叫“抢锁”。

事情没这么简单,上面这个简单的方案隐藏着好几个坑,这也是网上讨论最多的地方。
第一个大坑就是上面提到的“锁的过期时间”,设置短了不行,万一任务执行时间超过了10秒,锁自己失效了,别的进程就能进来了,结果还是重复执行,设置长了也不行,万一持有锁的进程真的崩溃了,其他进程要等很久才能获取到锁,系统的可用性就差了,通常需要根据业务逻辑的平均执行时间来评估一个相对安全的过期时间。
第二个大坑被叫做“误删别人的锁”,想象一下这个场景:进程A获取了锁,设置10秒过期,但是A执行任务花了15秒,在第10秒的时候,锁因为过期自动释放了,这时进程B瞅准机会成功获取了锁,然后到了第15秒,A任务执行完了,它全然不知锁已经不属于自己了,依然按照流程去释放锁(删除key),这一删,就把B刚拿到手的锁给删了!这下可好,进程C可能趁机又抢到了锁,整个系统就乱套了。

怎么解决这个问题呢?办法就是在设置锁的时候,value不要用固定的字符串,而是用一个唯一的、随机生成的值,比如UUID,在删除锁的时候,要先判断一下当前锁的value是不是自己当初设置的那个UUID,只有是自己的,才能删,这个“判断+删除”的操作必须是原子性的,不能分两步执行,否则可能判断完是自己的之后,锁又过期被B抢去了,还是会发生误删,通常要用Lua脚本来保证这个操作的原子性。
第三个问题是关于“锁的可重入性”,如果一个进程内,同一个线程多次请求同一把锁,比如一个方法递归调用自己,那这个锁应该能识别出是同一个持有者,并允许它再次进入,而不是把自己阻塞住,这在简单的SETNX方案里是实现不了的,需要更复杂的设计,比如在value里记录线程标识和重入次数,不过在很多场景下,如果业务逻辑能避免重复加锁,这个特性不是必须的。
第四个问题是关于“锁的公平性”,我们上面的方案是非公平的,所有没抢到锁的进程都在同时重试,谁先抢到算谁的,可能会存在某些进程一直饿死(永远抢不到)的情况,更高级的实现会使用Redisson这样的客户端库,它内部使用了Redis的队列和发布订阅机制,可以实现公平的锁,也就是先来后到。
还有一个更复杂的讨论是关于Redis集群环境下的安全问题,Redlock”算法,因为在主从复制的Redis架构中,如果主节点挂了,锁信息可能还没来得及同步到从节点,从节点升级为主节点后,锁信息就丢失了,另一个客户端就可能再次获取到锁,Redlock算法通过同时向多个独立的Redis实例申请锁,来试图解决这个问题,但这个算法本身也引发了很多争议,比如关于系统时钟漂移可能带来的影响。
用一个简单的Redis命令来实现分布式锁,想法很直观,但真要把它做得安全、健壮,需要考虑非常多的细节,从最初的SETNX和EXPIRE分开可能导致的死锁,到锁过期时间设置的两难,再到误删锁和集群环境下的可靠性,每一步都有坑,这也是为什么对于大多数生产环境,开发者更推荐使用像Redisson这样经过充分测试的客户端库,而不是自己从头去实现的原因,这些库已经把上述的坑都填平了,提供了可重入、公平、红锁等多种分布式锁实现,让开发者能更专注于业务逻辑本身。
本文由寇乐童于2026-01-24发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/84966.html
