redission 分布式锁 可重入锁(三)解锁

241 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

1 前言

研究源码 是为了解决实际问题 - 雨夜

zl(沈阳)-皓哥 是我目前遇到的最好的老大

先看 思考和应用场景的问题 思考下,然后再继续往下看

如果可以的话 点个赞/评论下 哈哈 好有点动力 有问题 也请留言

2 上节回顾/本节重点/上节思考题

2.1 上节

讲了加锁/watch dog 额外还有时间轮

2.2 本节讲解

1. 调用unlock 主动释放锁
2  宕机自动释放锁

3 思考

image.png

4 应用场景

5 unlock 步骤

5.1 RedissonLock 类 unlockInnerAsync方法

     5.1.1 
             if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then        
                  return nil;
            end;
             local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1)
            if (counter > 0) then   
                redis.call('pexpire', KEYS[1], ARGV[2]);
                   return 0
            else   
                redis.call('del', KEYS[1]);    
                redis.call('publish', KEYS[2], ARGV[1]);  
                return 1
            end;
             return nil;
5.1.2 方法执行之后 参数
    RedissonLock.EXPIRATION_RENEWAL_MAP
        key 例子 0dd5078f-f36a-4eaa-bf45-43b50871de62:lockKeyForWatchDog
        value RedissonLock.ExpirationEntry

5.2 参数

KEYS[1] lockKey
KEYS[2] getChannelName
        实际 redisson_lock__channel:{lockKey}
ARGV[1] LockPubSub.UNLOCK_MESSAGE
        实际 0
ARGV[2] this.internalLockLeaseTime
        实际 30000
ARGV[3] this.getLockName(threadId)
        07275c80-686c-4160-ac10-898def01d893:1
        

5.3 参数带入 解析

# 如果没锁过lockKey 直接返回null\
if (redis.call('hexists', lockKey, 07275c80-686c-4160-ac10-898def01d893:1) == 0then        \
      return nil;\
end;\
# 计数器 unlock 会把value 减一 也就是解一次锁\
 local counter = redis.call('hincrby', lockKey, 07275c80-686c-4160-ac10-898def01d893:1, -1); \
# 解一次锁之后 锁还被使用着\
if (counter > 0then   \
    # lockKey 重置时间\
    redis.call('pexpire', lockKey, 30000);\
       return 0; \
else   \
    # 锁被释放了 删除lockKey\
    redis.call('del', lockKey);    \
    redis.call('publish'redisson_lock__channel:{lockKey}, lockKey);  \
    return 1; \
end;\
 return nil;

5.4 收获

锁不存在 返回null
没解开 返回0 也就是false
锁解开了 返回1 也就是 true

5.5 里面有一个publish

redis.call('publish', redisson_lock__channel:{lockKey}, lockKey)

这是干什么的 有什么用?

广播解锁消息,去唤醒那些争抢过锁但还处于阻塞中的线程

6 基本概念

7 前面思考和应用场景解答 (在此之前 请再次思考前面的问题 )

7.1 多次解锁同一个lockKey 有什么返回

RLock rLock = redissonClient.getLock("lockKey");
rLock.lock();
rLock.unlock();
rLock.unlock();

返回信息

java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: e2b12746-6961-4d7b-94d8-cc9e309dc410 thread-id: 1

报错的原因

fa
public RFuture<Void> unlockAsync(long threadId) {
    RPromise<Void> result = new RedissonPromifase();
    //这就是那个lua 返回值请看5.4 
    RFuture<Boolean> future = this.unlockInnerAsync(threadId);
    future.onComplete((opStatus, e) -> {
        this.cancelExpirationRenewal(threadId);
        if (e != null) {
            result.tryFailure(e);
        //**opStatus 就是lua返回值 如果为null 代表 没有这个lockKey**
        } else if (opStatus == null) {
            IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + this.id + " thread-id: " + threadId);
            result.tryFailure(cause);
        } else {
            result.trySuccess((Object)null);
        }
    });
    return result;
}

7.2 如果解锁的是别人的锁 什么返回

返回信息 Exception in thread "Thread-8" java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: eccf8054-4f90-4b59-8c07-aabd71dfb179 thread-id: 65

报错原因和上面一摸一样

收获

解锁 无论多次解锁还是 解错lockKey 都会报错,可能在工作中就会存在解锁失败 导致的事务回滚问题

8 总结:

Redission 可重入锁(三).png

9 下节预知

  1. 公平锁 讲解

之后添加的额外内容:(先记下 定期添加) 已完成 1 时间轮(单机/分布式 和 对应的应用场景) 2 redission 面试题解析

10 目标:

  1. 随着学习 把一些坑解决 形成一个 redission的基础组件

代码地址: gitee.com/gf-8/yuye-p…

项目: yuye-test-redission

类地址: 对应的test 包之下 UnLockApplicationTests类

11 思考题

本节说了 要防止lockKey 解锁带来的回滚事务等问题,那怎么才能避免呢?为什么能避免?