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

Redis 分布式锁到底是个啥,怎么用来解决多节点抢资源的问题呢

(主要参考了Redis官方文档对分布式锁的建议实现,以及马丁·克莱普曼等技术专家关于分布式系统的讨论)

Redis分布式锁,就是用一个放在中心位置的Redis服务器,来当所有程序节点的“公证人”,帮大家决定在某一时刻,谁有权利去使用那个宝贵的共享资源,你可以把它想象成一个在线上虚拟世界的“一把钥匙”,哪个程序节点先抢到这把钥匙,哪个节点就获得了进门操作资源的资格,其他节点只能乖乖排队等着。

为什么需要这么一把“钥匙”呢?想象一个真实的场景:一个热门商品库存只剩最后一件,成千上万的请求同时涌来,要下单购买,这些请求很可能被负载均衡器分发到了后端不同的服务器节点上,如果每个节点都只检查自己内存里的数据(都看到库存是1),然后都认为可以卖出,那么结果就是“超卖”,一件商品被卖出了无数份,这显然是灾难性的,问题的根源就在于,这些分散在不同机器上的程序节点,它们的内存是独立的,彼此之间无法直接知道对方在干什么,这种时候,我们就需要一个它们都能访问的、中心化的“裁判”来协调顺序,确保同一时间只有一个节点能执行“减少库存”这个关键操作,Redis,凭借其高性能和简单易用的数据结构,就常常被选为这个“裁判”。

这把“锁”具体在Redis里是怎么实现的呢?最核心的命令就是SET key value NX PX timeout,我们来拆解一下这个命令:

  • SET:就是设置一个键值对。
  • key:这就是我们所说的“锁”的名字,对于商品A的库存,锁的key可以叫lock:stock:A,所有想操作这个库存的节点,都来争抢这个key的控制权。
  • value:这个值不能是随便写的,它必须是一个全局唯一的字符串,比如可以是一个UUID,为什么需要唯一?这是为了确保每个客户端持有的锁都是可识别的,这在后续安全释放锁时至关重要。
  • NX:这是关键参数,意思是“Only set the key if it does not already exist”(仅当key不存在时才设置),这保证了只有一个客户端能成功创建这个key,也就是抢到锁,如果key已经存在,其他客户端的SET操作就会失败,代表抢锁失败。
  • PX timeout:这指定了key的过期时间,单位是毫秒,比如PX 30000表示30秒后自动过期,设置过期时间是一个极其重要的安全措施,是为了防止死锁,万一某个节点抢到锁之后,还没来得及释放就因为宕机、网络问题或者程序BUG“挂掉”了,那么这把锁就会永远留在Redis里,其他节点再也无法获得锁,整个系统就卡死了,有了过期时间,即使持有锁的节点出事,锁也会在超时后自动释放,让其他节点有机会继续工作。

一个节点抢锁的过程就是:向Redis发送SET lock:resource_name unique_value NX PX 30000命令,如果Redis返回成功,那么这个节点就成功获得了锁,可以放心地去执行它那些需要独占资源的业务逻辑了,比如扣减库存、修改重要数据等。

业务逻辑执行完毕后,这个节点必须释放锁,好让等待的节点有机会工作,释放锁可不是简单地用DEL命令删除那个key就行了,这里有个经典的陷阱,如果节点A在执行业务逻辑时,因为某些原因(比如进行了耗时的Full GC)导致执行时间超过了锁的过期时间(30秒),那么Redis会自动把锁删除,节点B看到锁没了,就成功地抢到了锁,过了一会儿,节点A从GC中恢复过来,傻乎乎地继续执行DEL命令,结果就把节点B刚创建的锁给删掉了!这会导致锁的安全性被彻底破坏。

为了解决这个问题,释放锁时需要“验明正身”,正确的做法是,使用Lua脚本(因为Lua脚本在Redis中是原子性执行的)先检查当前锁的value是否还是自己当初设置的那个unique_value,只有是自己的那把锁,才允许删除,伪代码逻辑如下:

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

通过这套机制,Redis分布式锁就能以一个相对简单的方式,在多节点环境下实现基本的互斥访问,解决“抢资源”的难题,但必须清醒地认识到,它并不是一个完美的方案(参考马丁·克莱普曼的文章指出在故障场景下可能存在边界情况),它依赖于一个中心化的Redis服务,如果这个服务挂了,整个锁服务就不可用了(虽然可以用Redis集群模式提高可用性,但会引入新的复杂性),它适用于那些对一致性要求不是极端苛刻,但需要高性能和高并发控制的场景,对于更苛刻的场景,可能需要考虑ZooKeeper或etcd等专门为分布式协调而设计的系统。

Redis 分布式锁到底是个啥,怎么用来解决多节点抢资源的问题呢