Redisson框架实现Redis分布式锁的实现原理

一、前言

先看一段Redisson框架调用

RLock lock = redisson.getLock("myLock");
lock.lock();
//.......业务代码
lock.unlock();

Redisson支持redis单实例、redis哨兵、redis cluster、redis master-slave等各种部署架构

二、Redisson实现redis分布式锁的底层原理

1、加锁机制

以redis cluster集群为例,现在某个客户端要加锁,它首先会hash节点选择一台机器。(这里只是选择一台机器,还没有加锁),接着,会发送一段lua脚本到redis上,脚本如下:(用lua脚本的原因:就是为了保证下面指令的原子性)

//判断KEYS[1]是否存在,如果不存在,说明还没有客户端获取锁
"if(redis.call('exists',KEYS[1])==0) then "+ 
      //key不存在,那么用hset命令加锁,field为:客户端id value为:1(客户端获取了一次锁)
     "redis.call('hset',KEYS[1],ARGV[2],1); "+ 
     //设置这个锁的过期时长为 ARGV[1]
     "redis.call('pexpire',KEYS[1],ARGV[1]); "+
     //返回
     "return nil; "+
//if结束     
"end; "+
//如果KEYS[1]已经存在,那么再判断field为ARGV[2]的值是否存在,即是:判断客户端ARGV[2]是否获得了锁
"if(redis.call('hexists',KEYS[1],ARGV[2])==1) then "+
     //如果客户端获取了锁,那么将field的值加1,即是:将对应客户端获取锁的次数加1
     "redis.call('hincrby',KEYS[1],ARGV[2],1); "+
     //设置这个key下field值过期时长
     "redis.call('pexpire',KEYS[1],ARGV[1]); "+
     //返回
     "return nil; "+
//if结束     
"end; "+
//key已经存在,并且获取锁的客户端id不是当前执行方法的客户端id,返回锁key的剩余生存时间
//此时客户端2会进入一个while循环,不停的尝试加锁。
"return redis.call('pttl',KEYS[1]);";

参数:

  • KEYS[1]:代表的是你加锁的那个key
    比如:RLock lock = redisson.getLock("myLock");
  • ARGV[1]:代表的就是锁key的默认生存时间,默认30秒。
  • ARGV[2]:代表的是加锁的客户端的ID,类似于下面这样:
    34634f3f3b-2342-3244-87fd-34234efdsf3423f34f:1

首先用exists myLock命令判断,如果要加的锁key不存在,
那么就用hset myLock 34634f3f3b-2342-3244-87fd-34234efdsf3423f34f:1 1 命令加锁,设置key为myLock,field为客户端id ,值为1 的hash值;接着执行pexpire myLock 30000,设置myLock锁 key的生存时间是30秒。

2、锁的互斥

如果客户2现在也来尝试加锁:
判断exists myLock已经存在,那么执行第二个If 块,判断myLock锁key的hash数据结构中,是否包含客户端2的ID,但是明显不是的,因为那里包含的是客户端1的ID。
所以,客户端2会执行pttl myLock,获取myLock这个锁key的剩余生存时间。比如还剩10000ms,此时客户端2进入一个while循环,不停的尝试加锁。

3、watch dog自动延期机制

客户端1加锁的锁key默认生存时间30秒,如果超过了30秒,客户端1还想一直持有这把锁,怎么办呢?

watch dog自动延期机制:只要客户端1一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。

4、可重入加锁机制

客户端如果已经持有了锁,如果此时 再尝试获取锁 会怎样?比如:

RLock lock = redisson.getLock("myLock");
lock.lock();
//.....
lock.lock();
//.....
lock.unlock();
lock.unlock();

第一个if判断肯定不成立,“exists myLock”会显示锁key已经存在了。
第二个if判断会成立,因为myLock的hash数据结构中包含的那个ID,就是客户端1的那个ID,此时就会执行可重入锁的逻辑incrby myLock 34634f3f3b-2342-3244-87fd-34234efdsf3423f34f:1 1,将field为客户端1的值加1,变成2。就是将获取锁的次数变成了2

5、锁的释放机制

执行lock.unlock(),就可以释放分布式锁,实际上就是每次都对myLock数据结构中的那个加锁次数减1。如果发现加锁次数是0了,说明这个客户端已经不再持有锁了,此时就会用:
del myLock命令,从redis里删除这个key。
其它的客户端就可以尝试完成加锁

6、Redisson实现Redis分布式锁的缺点

上面方案最大的问题就是:如果你对某个redis master实例,写入了myLock这种锁key的value,此时会异步复制给对应的master slave实例。
但是这个过程中一旦发生redis master宕机,主备切换,redis slave变为了redis master。
接着就会导致,客户端2来尝试加锁的时候,在新的redis master上完成了加锁,而客户端1也以为自己成功加了锁。
此时就会导致多个客户端对一个分布式锁完成了加锁。
这时系统在业务语义上一定会出现问题,导致各种脏数据的产生。
所以这个就是redis cluster,或者是redis master-slave架构的主从异步复制导致的redis分布式锁的最大缺陷:在redis master实例宕机的时候,可能导致多个客户端同时完成加锁。

7、总结

根据上面解析,那么可总结出一张redisson获取锁的流程图如下:
Redisson框架实现Redis分布式锁的实现原理_第1张图片

参考:
https://mp.weixin.qq.com/s?__biz=MzU0OTk3ODQ3Ng==&mid=2247483893&idx=1&sn=32e7051116ab60e41f72e6c6e29876d9&chksm=fba6e9f6ccd160e0c9fa2ce4ea1051891482a95b1483a63d89d71b15b33afcdc1f2bec17c03c&scene=21#wechat_redirect

你可能感兴趣的:(分布式锁)