前言
上面这里是根据上一节中分析的Redis锁的Demo去分析具体这里面的代码或者说是Lua脚本是如何去获取锁的具体分析,具体代码在这里。Redisson
1.分析Lua脚本获取重入锁的分析
前提: 在执行redissonLock.lock()方法开始,进入的流程如下:
##主要是实现加锁和锁的续命redissonLock.lock();##深入执行的代码@Overridepublic void lock() {try {lockInterruptibly();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}##进入lockInterruptibly方法内部@Overridepublic void lockInterruptibly() throws InterruptedException {lockInterruptibly(-1, null);}##进入重参lockInterruptibly方法内部@Overridepublic void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {// 获取当前线程IDlong threadId = Thread.currentThread().getId();// 尝试获取锁的剩余时间Long ttl = tryAcquire(leaseTime, unit, threadId);// lock acquired ttl为空,说明没有线程持有该锁,直接返回 让当前线程加锁成功if (ttl == null) {return;}RFuture<RedissonLockEntry> future = subscribe(threadId);commandExecutor.syncSubscription(future);// 死循环try {while (true) {// 再此尝试获取锁的剩余时间 ,如果为null, 跳出循环ttl = tryAcquire(leaseTime, unit, threadId);// lock acquiredif (ttl == null) {break;}// waiting for message 如果ttl >=0 说明 有其他线程持有该锁if (ttl >= 0) {// 获取信号量,尝试加锁,设置最大等待市场为ttlgetEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} else {// 如果ttl小于0 (-1 ,-2 ) 说明已经过期,直接获取getEntry(threadId).getLatch().acquire();}}} finally {unsubscribe(future, threadId);}// get(lockAsync(leaseTime, unit));}##获取锁方法tryAcquireAsync(leaseTime, unit, threadId)方法private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {return get(tryAcquireAsync(leaseTime, unit, threadId));}private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {if (leaseTime != -1) {return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);}// 刚开始 leaseTime 传入的是 -1 ,所以走这个分支// 1)尝试加锁 待会细看 先把主要的逻辑梳理完RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);// 2) 注册监听事件ttlRemainingFuture.addListener(new FutureListener<Long>() {@Overridepublic void operationComplete(Future<Long> future) throws Exception {if (!future.isSuccess()) {return;}Long ttlRemaining = future.getNow();// lock acquiredif (ttlRemaining == null) {// 3)获取锁成功的话,给锁延长过期时间scheduleExpirationRenewal(threadId);}}});return ttlRemainingFuture;}// 1)尝试加锁 待会细看 先把主要的逻辑梳理完RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
结果:上面最后其实归纳一句就是执行该方法tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command)
内部就是执行Lua脚本,下面具体就是分析这个
关于Lua第一阶段脚本的分析
- Lua脚本如下(非公平锁),具体就是对相关的逻辑细节分析(在
tryLockInnerAsync方法内的Lua脚本): ```lua // 如果 lockKey不存在 ,设置 使用hset设置 lockKey ,field为 uuid:threadId ,value为1 ,并设置过期时间
//就是这个命令
//127.0.0.1:6379> hset lockkey uuid:threadId 1 //(integer) 1 //127.0.0.1:6379> PEXPIRE lockkey internalLockLeaseTime
“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; “ +
> 备注:> KEYS[1] ---------> getName()<br />ARGV[1] ---------> internalLockLeaseTime<br />ARGV[2] ---------> getLockName(threadId) 实现 id : threadId- 在执行`redis.call('exists', KEYS[1]) == 0 `中判断 KEYS[1] 对应的锁名是否存在,不存在就执行下面Lua脚本- 在执行`redis.call('hset', KEYS[1], ARGV[2], 1)`就是对应`hset lockkey uuid:threadId 1`指令创建`lockkey`的key,并对对其内部设置的键值对大致如下```json//其json对应的key为lockkey,其value值如下{"8743c9c0-0795-4907-87fd-6c719a6b4586:1":1}当中“8743c9c0-0795-4907-87fd-6c719a6b4586:1”对应值1为可重入做的次数,假如重入了就会叠加1,释放时候会减1,直到为0就释放锁
- 执行
redis.call('pexpire', KEYS[1], ARGV[1])就是对应执行PEXPIRE lockkey internalLockLeaseTime指令,对lockkey设定生命时间,其中使用[PEXPIRE ](http://redis.cn/commands/pexpire.html)是因为设置的key生命时间十一毫秒为单位,而[EXPIRE](http://redis.cn/commands/expire.html)则为秒为单位关于Lua第二阶段Redisson为何重入原理
这是链接上面第一阶段的代码分析之后是怎么操作的,代码如下: ```lua
// 如果 lockKey 存在和 filed 和 当前线程的uuid:threadId相同 key 加1 ,执行多少次 就加多次 设置过期时间 其实就是如下命令 //127.0.0.1:6379> HEXISTS lockkey uuid:threadId //(integer) 1 //127.0.0.1:6379> PEXPIRE lockkey internalLockLeaseTime
“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; “ +
// 最后返回 lockkey的 pttl
“return redis.call(‘pttl’, KEYS[1]);”
- 分析第一个判断指令`redis.call('hexists', KEYS[1], ARGV[2]) == 1`测主要判断lockkey是否存在外,还判断其内部结构`8743c9c0-0795-4907-87fd-6c719a6b4586:1`这个键也是否存在。【具体对`hexists`与`exists`的区别,下面补充资料中有提到】
- 假如lockkey及其内部 `id : threadId`存在,就执行`redis.call('hincrby', KEYS[1], ARGV[2], 1)`指令,给对应的`id : threadId`值加1,数据如下:
```json
lockkey:{
"8743c9c0-0795-4907-87fd-6c719a6b4586:1":1
}
hincrby指令变为如下
lockkey:{
"8743c9c0-0795-4907-87fd-6c719a6b4586:1":2
}
- 接着执行
redis.call('pexpire', KEYS[1], ARGV[1])指令,给keylock再次添加有效时间。 - 假如
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1)条件不成立,就返回nil,获取不到锁资源,然后退出截取资料
- 使用Redisson实现可重入分布式锁原理
- Redis进阶- Redisson分布式锁实现原理及源码解析
- Redis中文网之命令
补充资料
redis指令中
hexists与exists的区别?
举例redis存储的机构如下
KEY1:{ "KEY2":VALUE2 }**exists**是判断最外层key1是否存在,**hexists**是判断一个hash结构内部的subkey是否存在**exists key1 key2**redis执行Lua脚本时,那些具有原子性?
Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行: 当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。 这和使用 MULTI / EXEC 包围的事务很类似。 在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。 另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。写一个跑得很快很顺溜的脚本并不难, 因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,请小心, 因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。【Redis中文官网截取】
