当前位置: 首页 > 后端技术 > Java

Redis分布式锁的实现原理

时间:2023-04-01 17:32:34 Java

目前基于Redis的分布式锁常用的框架是Redisson,使用起来比较简单。项目中引入Redisson的依赖,然后基于RedisLock实现分布式锁的加锁和释放,如下://获取锁RLocklock=redisson.getLock("myLock");//加锁lock.lock();//业务代码//...//释放锁lock.unlock接收接下来说说Redisson框架对Redis分布式锁的实现原理。锁定机制某个客户端需要锁定。如果客户端面对的是一个RedisCluster,它会先根据hash节点选择一台机器。注意这里只选择了一台机器。然后会发送一个lua脚本给redis。lua脚本如下:为什么要用lua脚本?因为lua脚本可以保证原子性!上面lua脚本的意思是:KEYS[1]代表你锁定的key,例如:RLocklock=redisson.getLock("myLock");在这里你自己设置锁定的锁定密钥是“myLock”。ARGV[1]表示锁定密钥的默认生命周期,默认为30秒。ARGV[2]表示被锁定的客户端的ID,类似如下:8743c9c0-0795-4907-87fd-6c719a6b4586:1第一个if判断语句是使用“existsmyLock”命令判断,如果如果您要锁定的锁定键不存在,您将锁定它。如何锁定它?很简单,使用如下命令:hsetmyLock8743c9c0-0795-4907-87fd-6c719a6b4586:11通过这条命令设置一个哈希数据结构,执行这条命令后,会出现类似下面的数据结构:以上表示客户端“8743c9c0-0795-4907-87fd-6c719a6b4586:1”已经锁定了锁钥“myLock”。然后将执行“pexpiremyLock30000”命令,将锁定密钥myLock的生命周期设置为30秒。至此,锁定完成。锁互斥机制如果客户端B此时尝试加锁,则执行相同的lua脚本。第一个if判断会执行“existsmyLock”,发现锁keymyLock已经存在。然后第二次if判断myLock锁key的hash数据结构中是否包含客户端B的ID,显然不是,那么客户端B会得到一个pttlmyLock返回的数字,代表锁keymyLock存活时间的剩余值.此时客户端B会进入一个while循环,不断尝试加锁。看门狗自动延期机制客户端A锁定的lockkey的默认生命周期只有30秒。如果超过30秒,客户端A还想持有锁,怎么办?其实只要clientA加锁成功,就会启动看门狗。它是一个后台线程,每10秒检查一次。如果客户端A还持有lockkey,则继续延长lockkey的生命周期。可重入锁机制ClientA已经持有锁,然后可以重入锁,如下代码所示:此时lua脚本是这样执行的:第一个if判断不成立,"existsmyLock"会显示锁定密钥已经存在。第二次if判断会为真,因为myLock的hash数据结构中包含的ID是客户端A的ID,此时会执行重入锁的逻辑,会使用"incrbymyLock8743c9c0-0795-4907-87fd-6c71a6b4586:11"这条命令将客户端A的锁数加1,此时myLock的数据结构变成如下:即myLock的hash数据结构中clientID对应取决于锁的数量。释放锁机制执行lock.unlock()释放分布式锁。释放逻辑是:每次myLock数据结构中的锁数减1,如果锁数为0,则表示客户端不再持有锁,将使用“delMyLock”命令这时,从redis中删除了这个key。然后另一个客户端B可以尝试完成锁定。上述Redis分布式锁的缺点上述方案最大的问题在于,如果将myLock等锁key的值写入到一个redis主实例中,此时会被异步复制到对应的主从实例中时间,但是这个过程如果发送redismaster宕机,主备切换,redisslave变成redismaster。这样会导致客户端B在尝试加锁的时候完成了对新的redismaster的加锁,而客户端A也认为自己加锁成功了。类型锁已完成锁定。这样就会导致各种脏数据的产生。所以这就是redis集群造成的redis分布式锁最大的缺陷,或者说redis主从架构的主从异步复制:当redismaster实例宕机时,可能导致多个客户端同时完成加锁。