Redis 笔记十二 Redisson分布式锁的使用

Redisson分布式锁的实现原理
Redis 笔记十二 Redisson分布式锁的使用_第1张图片 加锁机制
如果该客户端面对的是一个 redis cluster 集群,他首先会根据 hash节点选择一台机器。发送 lua 脚本到 redis 服务器上,脚本如下
"if (redis.call('exists',KEYS[1])==0) then "+ --看有没有锁
"redis.call('hset',KEYS[1],ARGV[2],1) ; "+ --无锁 加锁 "redis.call('pexpire',KEYS[1],ARGV[1]) ; "+ "return nil; end ;" + "if 
(redis.call('hexists',KEYS[1],ARGV[2]) ==1 ) then "+ --我加的锁 
"redis.call('hincrby',KEYS[1],ARGV[2],1) ; "+ --重入锁 
"redis.call('pexpire',KEYS[1],ARGV[1]) ; "+ "return nil; end ;" + "return 
redis.call('pttl',KEYS[1]) ;" --不能加锁,返回锁的时间
lua的作用:保证这段复杂业务逻辑执行的原子性。
lua 的解释:
KEYS[1]) : 加锁的 key
ARGV[1] key 的生存时间,默认为 30
ARGV[2] : 加锁的客户端 ID ( UUID.randomUUID() + “:” + threadId )
第一段 if 判断语句,就是用 “exists myLock” 命令判断一下,如果你要加锁的那个锁 key不存在的话,你就进行加锁。如何加锁呢?很简单,用下面的命令:
hset myLock
8743c9c0-0795-4907-87fd-6c719a6b4586:1 1
通过这个命令设置一个 hash 数据结构,这行命令执行后,会出现一个类似下面的数据结构:myLock :{"8743c9c0-0795-4907-87fd-6c719a6b4586:1":1 }
上述就代表 “8743c9c0-0795-4907-87fd-6c719a6b4586:1” 这个客户端对 “myLock” 这个锁 key 完成了加锁。
接着会执行 “pexpire myLock 30000” 命令,设置 myLock 这个锁 key 的生存时间是 30 秒。
锁互斥机制
那么在这个时候,如果客户端 2 来尝试加锁,执行了同样的一段 lua 脚本,会咋样呢?
很简单,第一个 if 判断会执行 “exists myLock” ,发现 myLock 这个锁 key 已经存在了。
接着第二个 if 判断,判断一下, myLock key hash 数据结构中,是否包含客户端 2 ID ,但是明显不是的,因为那里包含的是客户端1 ID
所以,客户端 2 会获取到 pttl myLock 返回的一个数字,这个数字代表了 myLock 这个锁 key 剩余生存时 间。 比如还剩 15000 毫秒的生存时间。
此时客户端 2 会进入一个 while 循环,不停的尝试加锁
 
自动延时机制
只要客户端 1 一旦加锁成功,就会启动一个 watch dog 看门狗, 他是一个后台线程,会每隔 10 秒检查一 ,如果客户端 1 还持有锁 key ,那么就会不断的延长锁 key 的生存时间。
 
可重入锁机制
第一个 if 判断肯定不成立, “exists myLock” 会显示锁 key 已经存在了。
第二个 if 判断会成立,因为 myLock hash 数据结构中包含的那个 ID ,就是客户端 1 的那个 ID,也就是 “8743c9c0-0795-4907-87fd-6c719a6b4586:1” 此时就会执行可重入加锁的逻辑,他会用:
incrby myLock
8743c9c0-0795-4907-87fd-6c71a6b4586:1 1
通过这个命令,对客户端 1 的加锁次数,累加 1 。数据结构会变成: myLock :{"8743c9c0-0795-4907-87fd-6c719a6b4586:1": 2 }
释放锁机制
执行 lua 脚本如下:
#如果key已经不存在,说明已经被解锁,直接发布(publish)redis消息 
if (redis.call('exists', KEYS[1]) == 0) then  
    redis.call('publish', KEYS[2], ARGV[1]); 
    return 1;
end;
# key和field不匹配,说明当前客户端线程没有持有锁,不能主动解锁。 不是我加的锁 不能解锁 
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
    return nil;
end; 
# 将value减1 
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
# 如果counter>0说明锁在重入,不能删除key 
if (counter > 0) then 
    redis.call('pexpire', KEYS[1], ARGV[2]); 
    return 0;  
# 删除key并且publish 解锁消息 
else 
    redis.call('del', KEYS[1]);
    #删除锁 
    redis.call('publish', KEYS[2], ARGV[1]);    
    return 1; 
end;
return nil;
– KEYS[1] :需要加锁的 key ,这里需要是字符串类型。
– KEYS[2] redis 消息的 ChannelName, 一个分布式锁对应唯一的一个 channelName: “redisson_lockchannel {” + getName() + “}”
– ARGV[1] reids 消息体,这里只需要一个字节的标记就可以,主要标记 redis key 已经解锁,再结合redis的 Subscribe ,能唤醒其他订阅解锁消息的客户端线程申请锁。
– ARGV[2] :锁的超时时间,防止死锁
– ARGV[3] :锁的唯一标识,也就是刚才介绍的 id UUID.randomUUID() + “:” + threadId
 
如果执行 lock.unlock() ,就可以释放分布式锁,此时的业务逻辑也是非常简单的。其实说白了,就是每次都对myLock 数据结构中的那个加锁次数减 1 。如果发现加锁次数是0 了,说明这个客户端已经不再持有锁了,此时就会用:“del myLock”命令,从 redis 里删除这个 key 。 然后呢,另外的客户端2 就可以尝试完成加锁了。

你可能感兴趣的:(cache)