Redisson(redlock, redis锁, 分布式锁) 核心代码(加锁解锁)介绍说明

41 篇文章 1 订阅
38 篇文章 0 订阅

加锁核心代码(RedissonLock.tryLockInnerAsync):   

 

   <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('hset', 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));
    }


加锁lua脚本说明:

   加锁成功 返回nil:

     1. 如果key值不存在 设置key值:hset KEYS[1] ARGV[2] 1 设置过期时间: pexpire KEYS[1] ARGV[1]

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;

     2. 如果key值存在并且是自己线程的 设置key值+1: hincrby KEYS[1] ARGV[2] 1 并且设置过期时间 pexpire KEYS[1] ARGV[1] (这块是可重入的实现)

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;

   加锁失败 返回上一个锁的剩余过期时间:
      3. 加锁失败,其他情况(如果key值存在并且不是自己线程的 )返回剩余过期时间

return redis.call('pttl', KEYS[1]);

KEYS[1] = 加锁key
ARGV[2] = 加锁值(这里是key对应的hash的field) = id + ":" + threadId; (初始化RedissonClient是会生成ConnectionManager属性(ConfigSupport.createConnectionManager)设置好 id = UUID.randomUUID();(为的是可重入,和避免解锁时解错其他线程的锁))
ARGV[1] = 看门狗过期时间 internalLockLeaseTime(Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期)


解锁核心代码(RedissonLock.unlockInnerAsync):

    protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "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;",
                Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));

    }

解锁lua脚本说明:
    解锁成功:
        1. 如果key值不存在 返回nil

 if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil; end;

        2. 如果key值存在 设置并获取key值field为ARGV[3](当前线程的值)的值-1: hincrby KEYS[1] ARGV[3] -1 

local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 

         2.1. 如果减完后任然有锁(当前线程任然占用) 更新key值过期时间(看门狗的续期) pexpire KEYS[1] ARGV[2] 返回0

if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); return 0;

         2.2. 如果减完后小于等于0 删除key值 (真正的解锁成功)发布解锁消息 返回1

else redis.call('del', KEYS[1]); redis.call('publish', KEYS[2], ARGV[1]); return 1;end;

         3. 其他情况 返回nil

return nil;

 理论上不抛出异常解锁都是认为成功了的,当等于ture时意味着线程完全解锁
    KEYS[1] = 加锁key
    KEYS[1] = 发布频道: getChannelName()
    ARGV[1] = 解锁消息: LockPubSub.UNLOCK_MESSAGE
    ARGV[2] = 看门狗过期时间 internalLockLeaseTime(Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期)
    ARGV[3] = 加锁值(这里是key对应的hash的field) = id + ":" + threadId; (初始化RedissonClient是会生成ConnectionManager属性(ConfigSupport.createConnectionManager)设置好 id = UUID.randomUUID();(为的是可重入,和避免解锁时解错其他线程的锁))


这里需要明白为什么使用lua脚本,因为redis的lua脚本是原子性(事务)的,加锁解锁整个过程必须保证原子性(事务),具体的原理可以参考:《Redis官方文档》用Redis构建分布式锁

安全和可靠性保证
    在描述我们的设计之前,我们想先提出三个属性,这三个属性在我们看来,是实现高效分布式锁的基础。
    一致性:互斥,不管任何时候,只有一个客户端能持有同一个锁。
    分区可容忍性:不会死锁,最终一定会得到锁,就算一个持有锁的客户端宕掉或者发生网络分区。
    可用性:只要大多数Redis节点正常工作,客户端应该都能获取和释放锁。(单例模式这点做不到)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值