当前位置: 首页 > 科技观察

Redis分布式锁,没有它,真的不行

时间:2023-03-12 22:01:19 科技观察

写在前面现在面试一般都是讲分布式系统。通常面试官会从服务框架(SpringCloud、Dubbo)开始,一路讲分布式事务、分布式锁、ZooKeeper等知识。那么本文就来谈谈分布式锁的知识,具体看一下Redis分布式锁的实现原理。说实话,如果你在公司生产环境使用分布式锁,肯定会用到开源库,比如Redis分布式锁,一般就用Redisson框架,非常好用。有兴趣的可以去Redisson官网看看如何在项目中引入Redisson的依赖,进而实现基于Redis的分布式锁的加锁和释放。给大家看一个简单的代码片段,让我们直观感受一下:怎么样,上面的代码是不是感觉很简单?此外,它们还支持redis单实例、redissentinel、redis集群、redis主从等多种部署架构,可以为您完美实现。Redisson实现Redis分布式锁的底层原理还是不错的。下面通过一张手绘图,告诉大家Redisson这个开源框架是如何实现Redis分布式锁的。(1)锁定机制看上图。现在某个客户端需要锁定。如果客户端面对的是redis集群,他会先根据hash节点选择一台机器。这里注意,选机器就行!这个非常重要!紧接着,会向redis发送一个lua脚本。lua脚本如下:为什么要用lua脚本?因为可以将大量复杂的业务逻辑封装在一个lua脚本中,发送到redis中,保证这个复杂业务逻辑执行的原子性。那么,这个lua脚本是什么意思呢?KEYS[1]代表你锁定的密钥,例如:这里你设置锁定的锁定密钥是“myLock”。RLocklock=redisson.getLock("myLock")ARGV[1]表示锁key的默认生命周期,默认为30秒。ARGV[2]代表被锁定的客户端ID,类似如下:8743c9c0-0795-4907-87fd-6c719a6b4586:1给大家解释一下,第一个if判断语句是使用“existsmyLock”命令判断一下,如果你要加锁的锁key不存在,就加锁。如何锁定它?很简单,使用下面的命令:hsetmyLock8743c9c0-0795-4907-87fd-6c719a6b4586:11通过这个命令设置一个哈希数据结构。这行命令执行后,会出现类似如下的数据结构:上面表示客户端“8743c9c0-0795-4907-87fd-6c719a6b4586:1”已经锁定了锁钥“myLock”。然后执行“pexpiremyLock30000”命令将锁钥myLock的生命周期设置为30秒。ok,至此,ok,锁定完成。(2)锁互斥机制那么这个时候如果client2尝试加锁并执行同一个lua脚本,会发生什么情况呢?很简单,第一个if判断会执行“existsmyLock”,发现锁keymyLock已经存在。然后第二个if判断,判断myLock锁键的hash数据结构中是否包含客户端2的ID,显然不是,因为里面包含了客户端1的ID。因此,客户端2会得到一个pttlmyLock返回的数字,它代表锁键myLock的剩余生命周期。例如,还剩15000毫秒可活。此时client2会进入一个while循环,不断尝试加锁。(3)看门狗自动延期机制客户端1加锁的lockkey默认生命周期只有30秒。如果超过30秒,客户端1还想持有锁,怎么办?简单的!只要client1成功加锁,就会启动watchdog。它是一个后台线程,每10秒检查一次。如果client1还持有lockkey,则继续延长lockkey的生命周期。(4)可重入锁机制如果client1已经持有锁,那么可重入锁会怎样呢?比如下面的代码:这时候我们来分析一下上面的lua脚本。第一个if判断肯定不成立,“existsmyLock”会显示lockkey已经存在。第二个if判断会成立,因为myLock的hash数据结构中包含的ID是client1的ID,即“8743c9c0-0795-4907-87fd-6c719a6b4586:1”这时候,重现要进入加锁逻辑,他会使用:incrbymyLock8743c9c0-0795-4907-87fd-6c71a6b4586:11通过这个命令,客户端1的锁数加1,此时myLock数据结构变成如下:As可以看到,myLock的hash数据结构中的clientID对应着锁的数量。(5)释放锁机制如果执行lock.unlock(),就可以释放分布式锁,此时的业务逻辑也很简单。其实说白了就是每次myLock数据结构中的锁数减1。如果发现锁的数量为0,说明客户端不再持有锁。这时候会使用“delmyLock”命令从redis中删除key。然后,另一个客户端2可以尝试完成锁定。这就是所谓的分布式锁开源Redisson框架的实现机制。一般在生产系统中,我们可以使用Redisson框架提供的类库来实现基于redis的分布式锁的加锁和释放。(6)上述Redis分布式锁的缺点其实上面方案最大的问题是,如果把myLock等锁key的值写入某个redismaster实例,会被异步复制到相应的主人。从实例。但是这个过程中一旦redismaster宕机,主备切换,redisslave变成redismaster。那么就会导致客户端2尝试加锁的时候,加锁是在新的redismaster上完成的,而客户端1也认为加锁成功了。这时候,多个客户端会锁定一个分布式锁。这时候系统肯定会在业务语义上出现问题,导致各种脏数据的产生。所以这就是rediscluster造成的redis分布式锁最大的缺陷,或者说redis主从架构的主从异步复制:当redismaster实例宕机时,可能会导致多个客户端同时完成加锁。