这里列出一般情况下解锁和强制解锁的Lua脚本,分析如下:
-- unlockInnerAsync方法的lua脚本 -- KEYS[1] == getName() --> $KEY --> resource:x -- KEYS[2] == getChannelName() --> 订阅锁的Channel --> redisson_lock__channel:{resource:x} -- ARGV[1] == LockPubSub.UNLOCK_MESSAGE --> 常量数值0 -- ARGV[2] == internalLockLeaseTime --> 30000或者具体的锁最大持有时间 -- ARGV[3] == getLockName(threadId) --> 559cc9df-bad8-4f6c-86a4-ffa51b7f1c36:1 -- 第一个IF分支判断如果锁所在的哈希的field不存在,说明当前线程ID未曾获取过对应的锁,返回NULL表示解锁失败 if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil; end; -- 走到这里通过hincrby进行线程重入计数-1,返回计数值 local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); -- 计数值大于0,说明线程重入加锁,这个时候基于internalLockLeaseTime对锁所在KEY进行续期 if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); return 0; else -- 计数值小于或等于0,说明可以解锁,删除锁所在的KEY,并且向redisson_lock__channel:{$KEY}发布消息,内容是0(常量数值) redis.call('del', KEYS[1]); redis.call('publish', KEYS[2], ARGV[1]); return 1; end; -- 最后的return nil;在IDEA中提示是不会到达的语句,估计这里是开发者笔误写上去的,前面的if-else都有返回语句,这里应该是不可达的 return nil; -------------------------------------------------- 不怎么华丽的分割线 ------------------------------------------------- -- forceUnlockAsync方法的lua脚本 -- KEYS[1] == getName() --> $KEY --> resource:x -- KEYS[2] == getChannelName() --> 订阅锁的Channel --> redisson_lock__channel:{resource:x} -- ARGV[1] == LockPubSub.UNLOCK_MESSAGE --> 常量数值0 -- 强制删除锁所在的KEY,如果删除成功向redisson_lock__channel:{$KEY}发布消息,内容是0(常量数值) if (redis.call('del', KEYS[1]) == 1) then redis.call('publish', KEYS[2], ARGV[1]); return 1 else return 0 end其他辅助方法都相对简单,这里弄个简单的"流水账"记录一番:
isLocked():基于getName()调用Redis的EXISTS $KEY命令判断是否加锁
isHeldByThread(long threadId)和isHeldByCurrentThread():基于getName()和getLockName(threadId)调用Redis的HEXISTS $KEY $LOCK_NAME命令判断HASH中对应的field-value是否存在,存在则说明锁被对应线程ID的线程持有
getHoldCount():基于getName()和getLockName(threadId)调用Redis的HGET $KEY $LOCK_NAME命令,用于获取线程对于某一个锁的持有量(注释叫holds,其实就是同一个线程对某一个锁的KEY的续期次数)
订阅和发布部分设计到大量Netty组件使用相关的源码,这里不详细展开,这部分的逻辑简单附加到后面这个流程图中。最后,通过一个比较详细的图分析一下Redisson的加锁和解锁流程。
不带waitTime参数的加锁流程:
带有waitTime参数的加锁流程(图右边的流程基本不变,主要是左边的流程每一步都要计算时间间隔):
解锁流程:
假设不同进程的两个不同的线程X和Y去竞争资源RESOURCE的锁,那么可能的流程如下:
最后再概括一下Redisson中实现red lock算法使用的HASH数据类型:
KEY代表的就是资源或者锁,创建、存在性判断,延长生存周期和删除操作总是针对KEY进行的
FIELD代表的是锁名称lockName(),但是其实它由Redisson连接管理器实例的初始化UUID拼接客户端线程ID组成,严格来说应该是获取锁的客户端线程唯一标识
VALUE代表的是客户端线程对于锁的持有量,从源码上看应该是KEY被续期的次数
基于Jedis实现类似Redisson的分布式锁功能