redission 分布式锁 可重入锁(二)

1,399 阅读5分钟

组建了一个每日学习群 互相督促 已经开始一段时间了,有想法的可以 加微信 yuyezhiji 麻烦备注原因

————————————————————————————————————————————————————————————————————————————————————————

1 不要去 开课吧 问原因就 自己百度去

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

2.1 上节讲了加锁/解锁

2.2 本节讲解

1. watch dog
2 阿里规约关于分布式锁 为什么要那么写

2.3 上节 思考题解答

1 需要设置10s之后还拿不到锁就直接返回
    RLock lock = client.getLock(lockKey);
    lock.tryLock(10000, TimeUnit.MILLISECONDS);
2 如果获取到锁 10s之内 没有主动释放锁 那么就自动释放锁
    RLock lock = client.getLock(lockKey);
    lock.lock(lockKey,10000);//设置时间的时候 watch dog不生效
    

3 思考

image.png

4 应用场景

5 使用watch dog的步骤

5.1 RedissonLock 类 tryAcquireAsync 方法

     5.1.1 
             // 如果加锁成功 就会开启这个(ttlRemaining 为null 是加锁成功,
             // 如果不为空 代表这个锁还需要多少ms 存活时间 为什么请看上节 )
             ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                if (e == null) {
                    if (ttlRemaining == null) {
                        this.scheduleExpirationRenewal(threadId);
                    }
                }
5.1.2 方法执行之后 参数
    RedissonLock.EXPIRATION_RENEWAL_MAP
        key 例子 0dd5078f-f36a-4eaa-bf45-43b50871de62:lockKeyForWatchDog
        value RedissonLock.ExpirationEntry

5.2 renewExpiration

5.2.1 知识点
// 使用的时间轮 进行调度处理
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {\
                public void run(Timeout timeout) throws Exception {\
                    //本地存了一个map 根据entryName 找到对应的value\
                    RedissonLock.ExpirationEntry ent = (RedissonLock.ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());\
                    if (ent != null) {\
                        //value有值 threadIds 本来是一个数组 因为本应用场景 指挥放一个threadId (抢到锁的线程Id)\
                        Long threadId = ent.getFirstThreadId();\
                        if (threadId != null) {\
                            //时间续约的lua方法\
                            RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);\
                            future.onComplete((res, e) -> {\
                                if (e != null) {\
                                    //如果有 error 直接打印error 日志\
                                    RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);\
                                } else {\
                                    if (res) {\
                                        //如果加锁成功 lua返回1 代表true 就重新调用 RedissonLock.this.renewExpiration();\
                                        RedissonLock.this.renewExpiration();\
                                    }\
\
                                }\
                            });\
                        }\
                    }\
                }\
            //开启调度任务 30s/3 = 10s 调用一次\
            }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
5.2.2 watch dog 具体执行的lua
#。如果锁存在 就重新续约到30s 存在时间 返回1 true,否则 返回0 也就是false
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then\
         redis.call('pexpire', KEYS[1], ARGV[1]); \
         return 1;
 end
return 0;
5.2.3 带入参数的样子
#。如果锁存在 就重新续约到30s 存在时间 返回1 true,否则 返回0 也就是false
if (redis.call('hexists', lockKeyForWatchDog, 9aa6461a-1e1b-4806-9343-56b39e03bddd:1) == 1) then\
         redis.call('pexpire', lockKeyForWatchDog, 30000); \
         return 1end; \
return 0;
5.2.4 参数
KEYS[1]  lockKeyForWatchDog
ARGV[1]  30000
ARGV[2]  9aa6461a-1e1b-4806-9343-56b39e03bddd:1

5.2.5 处理
    如果有 error 直接打印error 日志
    如果加锁成功 lua返回1 代表true 就重新调用RedissonLock.this.renewExpiration();
    

6 基本概念

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

7.1 如果想改变 watch dog 调度时间 改变哪个参数

从本节 知道 internalLockLeaseTime 默认为30s 
this.internalLockLeaseTime / 3L 为调度时间
那么想改变watch dog 调度时间  就需要改变internalLockLeaseTime

是从哪设置的呢
this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
可以看到30000是直接代码里写的,只有一个 setLockWatchdogTimeout 可以改变
往回debug 可以看到 其实最开始是 

Redisson.create(Config config); 中的config
->
Redisson redisson = new Redisson(config);
->
把config 值重新赋值 

总结:
直接改变config中的 
config.setLockWatchdogTimeout(xxx)
只能统一改变

7.2 阿里规约关于 分布式锁的解读

阿里规约中 怎么规定的:
9.【强制】在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没\
有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。\
说明一:在 lock 方法与 try 代码块之间的方法调用抛出异常,无法解锁,造成其它线程无法成功获取锁。\
说明二:如果 lock 方法在 try 代码块之内,可能由于其它方法抛出异常,导致在 finally 代码块中,unlock 对未加锁的对\
象解锁,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),抛出 IllegalMonitorStateException 异常。\
说明三:在 Lock 对象的 lock 方法实现中可能抛出 unchecked 异常,产生的后果与说明二相同。\
正例:\
Lock lock = new XxxLock();
    // ...
    lock.lock();
    try {
    doSomething();
    doOthers();
    } finally {
    lock.unlock();
}

为什么这么写?


Lock lock = new XxxLock();
    //try 外面获取锁 但是try后面到try中间不允许有任务业务代码
    // 防止 因为因为a业务代码跑出异常 导致锁解不开
    lock.lock();
    //a()
    try {
        doSomething();
        doOthers();
    } finally {
        //这块上节说了 要加一个判断 
        // if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); }
        lock.unlock();
    }

7.3 如果先lock 获取锁 然后trylock 有什么效果

这个其实没有意义 就是一个key 加了2次锁 注意要解开2次锁

7.4 如果想实现执行调度任务,但是可能某个条件 需要通知调度任务怎么办

1. 开一个调度线程 多线程那一套
2  学完本节 多一种 用时间轮 每次调度之后 根据情况决定是否继续运行 自由度更高
    那这块有问题了 redission 我知道用了时间轮 做调度
    但是我们在实际开发的时候 怎么用呢
    请看 WheelTimerApplicationTests 类 wheelTimerTest 方法

HashedWheelTimer timer = new HashedWheelTimer(new DefaultThreadFactory("redisson-timer"), (long)500, TimeUnit.MILLISECONDS, 1024, false);
timer.newTimeout(new TimerTask() {
    public void run(Timeout timeout) throws Exception {
        System.out.println("1111");
    }
},  1000, TimeUnit.MILLISECONDS);
timer.newTimeout(new TimerTask() {
    public void run(Timeout timeout) throws Exception {
        System.out.println("222");
    }
},  1000, TimeUnit.MILLISECONDS);
Thread.sleep(10000);

8 总结:

Redission 可重入锁(二).png

9 下节预知

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

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

10 目标:

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

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

项目: yuye-test-redission

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

11 思考题

如果分布式锁 锁了10000个 key 每锁一个key就是开一个线程么