Redisson分布式锁学习总结:写锁 RedissonWriteLock#lock 获取锁源码分析

本文详细分析了RedissonWriteLock的内部实现,特别是使用Lua脚本来加锁的逻辑。当锁模式为空时,lua脚本会设置锁模式为写并记录线程信息,同时设置超时时间。如果锁模式已是写锁且当前线程已持有,lua脚本会增加加锁次数并更新过期时间,支持锁的可重入。在不满足这两种情况时,lua脚本返回锁的剩余超时时间,表示获取锁失败。RedissonWriteLock的lua脚本简化了写锁的管理,只记录写线程信息,而不需要加锁超时记录。

1、RedissonWriteLock 之 lua 脚本加锁

上两篇文章,我们已经分析了读锁 RedissonReadLock 的加锁和释放锁的执行原理。下面,我们直入主题,将先分析写锁 RedissonWriteLock 的加锁原理,至于 watchdog 机制中的 lua 脚本,RedissonWriteLock 和 RedissonLock 保持一致,不需和 RedissonReadLock 一样单独分析。

RedissonWriteLock#tryLockInnerAsync:

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

    return evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                        "local mode = redis.call('hget', KEYS[1], 'mode'); " +
                        "if (mode == false) then " +
                              "redis.call('hset', KEYS[1], 'mode', 'write'); " +
                              "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                              "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                              "return nil; " +
                          "end; " +
                          "if (mode == 'write') then " +
                              "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                                  "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + 
                                  "local currentExpire = redis.call('pttl', KEYS[1]); " +
                                  "redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " +
                                  "return nil; " +
                              "end; " +
                            "end;" +
                            "return redis.call('pttl', KEYS[1]);",
                    Arrays.<Object>asList(getName()), 
                    internalLockLeaseTime, getLockName(threadId));
}

我们可以看到,写锁获取锁的 lua 脚本不长,我们一步一步分析。

分析前,我们先定好,读写锁的key为 myLock:

RedissonClient client = RedissonClientUtil.getClient("");
RReadWriteLock readWriteLock = client.getReadWriteLock("myLock");

1.1、KEYS

Arrays.asList(getName()):

  • getName(): 锁key

KEYS:[“myLock”]

1.2、ARGVS

internalLockLeaseTime, getLockName(threadId):

  • internalLockLeaseTime:其实就是 watchdog 的超时时间,默认是30000毫秒,可看 Config#lockWatchdogTimeout。
  • getLockName(threadId):super.getLockName(threadId) + “:write” -> 客户端ID(UUID):线程ID(threadId):write

ARGVS:[30_000毫秒,“UUID:threadId:write”]

1.3、lua 脚本分析

1、第一步,获取锁模式

lua脚本

local mode = redis.call('hget', KEYS[1], 'mode'); 

分析:

  1. 利用 hget 命令获取当前锁模式
    hget myLock mode
    
2、分支一:锁的模式为空,即当前锁尚未被其他线程持有

场景:

  • 当前线程尝试获取写锁时,还没有其他线程成功持有锁,包括读锁和写锁

lua脚本:

"if (mode == false) then " +
  "redis.call('hset', KEYS[1], 'mode', 'write'); " +
  "redis.call('hset', KEYS[1], ARGV[2], 1); " +
  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
  "return nil; " +
"end; " +

分析:

  1. 利用 hset 命令设置锁模式为写锁

    hset myLock mode write
    

    执行后,锁内容如下:

    myLock:{
        "mode":"write"
    }
    
  2. 利用 hset 命令为当前线程添加加锁次数记录

    hset myLock UUID:threadId:write 1
    

    执行后,锁的内容如下:

    myLock:{
        "mode":"write",
        "UUID:threadId:write":1
    }
    

    我们可以发现,读写锁中的写锁获取锁不再需要写锁中的加锁超时记录,因为写锁仅支持一个线程来持有锁,锁的超时时间就是线程持有锁的超时时间。

  3. 利用 pexpire 命令为锁添加过期时间

    pexpire myLock 30000
    
  4. 最后返回nil,表示获取锁成功

3、分支二:锁模式为写锁并且持有写锁为当前线程,当前线程可再次获取写锁

场景:

  • 当前线程重复获取写锁,即读写锁中的写锁支持可重入获取锁

lua脚本:

"if (mode == 'write') then " +
  "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
      "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + 
      "local currentExpire = redis.call('pttl', KEYS[1]); " +
      "redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " +
      "return nil; " +
  "end; " +
"end;"

分析:

  1. 锁模式为写锁,利用 hexists 命令判断持有写锁为当前线程

    hexists myLock UUID:threadId:write
    
  2. 利用 hincrby 命令为当前线程增加1次加锁次数

    hincrby myLock UUID:threadId:write 1
    

    假设之前当前线程获取1次写锁,那么执行后,redis里写锁的相关数据:

    
    myLock:{
        "mode":"write",
        "UUID:threadId:write":2
    }
    

    我们可以看到,读写锁里面写锁在 redis 里面的数据,和 RedissonLock 相比,只多了一个mode字段来标识当前读写锁的模式;当然了,写锁也支持相同线程可重入获取锁。

  3. 利用 pttl 获取当前写锁的超时剩余毫秒数

    pttl myLock
    
  4. 利用 pexipre 给锁重新设置锁的过期时间,过期时间为:上次加锁的剩余毫秒数+30000毫秒

    pexpire myLock currentExpire+30000
    
  5. 最后返回nil,表示获取锁成功

4、最后:获取锁失败,返回锁pttl

场景:

  • 不满足上面的两个分支,当前线程就无法成功获取写锁

lua脚本:

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

分析:

  1. 利用 pttl 命令获取锁过期时间(毫秒)

    pttl myLock
    
  2. 直接返回步骤1的获取到的毫秒数

2、后续

因为 RedissonWriteLock 也是基于 RedissonLock 扩展的,所以关于 watchdog 和获取锁失败等机制,就不再详述了,和 RedissonLock 基本保持一致。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值