Redisson Lock--分布式锁

19 篇文章 0 订阅

流程

RedissonClient-(getLock)->RedissonLock-(lock)->tryAcquire-(同步转异步)->tryAcquireAsync-(最终调用)->tryLockInnerAsync-(lua脚本)->lua(脚本原子性,发送)–>redis服务器接受请求,执行脚本

源码

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);

        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                  "if (redis.call('exists', KEYS[1]) == 0) then " +
                      "redis.call('hincrby', 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]);",
                    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }

解析

● 参数

KEYS[1] 锁名称–Collections.singletonList(getName())
ARGV[1] 过期时间默认30s --internalLockLeaseTime
ARGV[2] 加锁的唯一标识, UUID + threadId—getLockName(threadId)

● lua加锁逻辑

1)先调用redis的exists命令判断加锁的key存不存在,如果不存在的话,那么就进入if。不存在的意思就是还没有某个客户端的某个线程来加锁,第一次加锁肯定没有人来加锁,于是第一次if条件成立。
2)然后调用redis的hincrby的命令,设置加锁的key和加锁的某个客户端的某个线程,加锁次数设置为1,加锁次数很关键,是实现可重入锁特性的一个关键数据。用hash数据结构保存。hincrby命令完成后就形成如下的数据结构。
myLock:{“b983c153-7421-469a-addb-44fb92259a1b:1”:1}
3)最后调用redis的pexpire的命令,将加锁的key过期时间设置为30s。

锁续约

● 默认30秒,是为了防止死锁
● 续约机制

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
        if (leaseTime != -1) {
            //加锁
            return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        }
        RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            if (e != null) {
                return;
            }

            // lock acquired
            if (ttlRemaining == null) {
                //定时任务,续约
                scheduleExpirationRenewal(threadId);
            }
        });
        return ttlRemainingFuture;
    }

//续约核心
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                    "return 1; " +
                "end; " +
                "return 0;",
            Collections.<Object>singletonList(getName()), 
            internalLockLeaseTime, getLockName(threadId));
    }

可重入锁

                  "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; " +

● 判断当前已经加锁的key对应的加锁线程跟要来加锁的线程是不是同一个,是就将这个线程对应的加锁次数+1,实现也可重入锁,同事返回nil回去。
//返回值

myLock:{
	"b983c153-7421-469a-addb-44fb92259a1b:1":2
}

主动释放锁和避免其他线程释放自己的锁

● 不手动释放的锁因为看门狗机制会造成死锁。
● 指定超时自动释放的会造成锁浪费。
● 释放锁会传入当前线程id避免释放其他锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值