当前位置:首页 > 问答 > 正文

Redis分布式锁怎么用才靠谱,聊聊那些坑和实操经验分享

说到用Redis实现分布式锁,很多人第一反应就是“SETNX”命令,确实,这个想法没错,但如果你只用了SETNX,那很可能已经踩进坑里了,网上有很多文章,比如早期一些博客园、CSDN的帖子,可能只介绍了最基本的方法,但实际生产环境用起来问题一大堆,下面我就结合自己用过的情况,聊聊怎么用才靠谱,以及那些年我踩过的坑。

第一个大坑:死锁问题。 这是最常见的问题,服务A抢到了锁,然后开始处理业务逻辑,结果还没执行完释放锁,服务A自己突然挂掉了(比如机器宕机或者网络闪断),这下好了,这个锁永远都不会被释放了,其他服务再也拿不到锁,整个系统就卡死了,这就是典型的死锁。

怎么解决死锁?给锁加个过期时间。 这是必须的一步,也就是说,就算拿到锁的服务挂了,等过了设定的时间,锁也会自动失效,其他服务还能继续抢,早期可能有人用SETNX命令设置锁,再用EXPIRE命令设置过期时间,但这两个命令不是原子操作,万一刚执行完SETNX,服务就挂了,EXPIRE没来得及执行,又会导致死锁。

靠谱的做法是使用原子命令。 Redis 2.6.12版本之后,SET命令增加了一系列参数,可以直接一条命令完成设值和设置过期时间,SET lock_key unique_value NX PX 30000,这里的NX就是“只有当key不存在时才设置”,PX是设置过期时间(单位毫秒),这条命令是原子性的,要么都成功,要么都失败,完美解决了设置值和设过期时间不一致的问题。

第二个坑:误删别人的锁。 这个坑我也踩过,场景是这样的:服务A拿到锁,设置的过期时间是30秒,但是A的业务逻辑比较复杂,执行了35秒还没完(可能是因为GC停顿,或者依赖的外部服务慢),这时候,锁因为到了30秒自动释放了,服务B一看锁没了,立马抢到了锁,过了5秒,服务A终于执行完了,它就去执行删除锁的操作,结果,它把服务B刚创建的锁给删掉了!这就乱套了。

解决这个问题,关键是要确保“谁加的锁,谁才能删”。 我们在设置锁的时候,不能只设置一个简单的key,lock_order”,而应该给这个锁设置一个唯一的值(unique_value),这个值可以是UUID或者请求ID等全局唯一的东西,在删除锁的时候,要先判断一下当前锁的值是不是自己设置的那个值,如果是,才能删除。

这里又有一个小坑:判断值和删除操作是两个独立的命令,不是原子的,如果你先GET lock_key,发现值匹配,再DEL lock_key,在这两个命令的间隙,锁可能因为过期又被别人抢走了,你还是有可能删错。

要使用Lua脚本来保证原子性。 Lua脚本在Redis中是原子执行的,我们可以写一个简单的脚本:

if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

这样,判断和删除的动作在服务器端一次性完成,就不会有并发问题了。

第三个值得讨论的点:过期时间设置多久合适? 这没有标准答案,完全取决于你的业务逻辑执行时间,设短了,容易出现上面说的锁提前释放、被别的服务抢走的情况,设长了,万一服务真挂了,其他服务要等待很长时间才能继续工作,一个常见的实践是,设置一个相对保守的时间,比如预估业务处理最大时间的2-3倍,还有一种思路是使用“看门狗”机制,也就是在持有锁期间,另起一个线程定期去检查锁是否还存在,如果还存在并且业务还没处理完,就自动延长锁的过期时间,这有点像Java中Redisson这个客户端库实现的逻辑,它帮我们封装好了这些复杂的细节。

关于Redis本身的问题。 我们上面讨论的都是基于Redis是单实例的情况,如果你用的是Redis主从架构,问题会更复杂,服务A在Master节点上成功创建了锁,但这个锁还没来得及同步到Slave节点,Master就宕机了,然后Slave被提升为新的Master,此时锁的信息就丢失了,服务B又能成功加锁,导致两个服务同时持有锁,对于这种极端情况,Redis官方提出了一个叫RedLock的算法,但它争议也挺大,实现起来更复杂,对性能也有影响,我的经验是,除非你的业务对一致性要求极高,能接受一定的性能损耗,否则大多数情况下,使用上面提到的单实例Redis锁+合理设置过期时间和唯一值,已经能解决90%以上的分布式锁需求了,如果真的需要万无一失,可能要考虑使用ZooKeeper或者etcd这类强一致性的协调服务来实现分布式锁。

一个相对靠谱的Redis分布式锁至少要做到:1. 使用SET NX PX/XE命令原子性加锁;2. 设置一个全局唯一的锁标识;3. 使用Lua脚本原子性解锁,在实际项目中,我强烈建议直接使用成熟的客户端库,比如Java的Redisson,它已经把这些最佳实践都封装好了,比自己从头实现要稳妥得多。

Redis分布式锁怎么用才靠谱,聊聊那些坑和实操经验分享