• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

【Redisson】二.可重入锁-lua脚本加锁逻辑源码

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

前言

  主要介绍Redisson可重入锁,通过lua脚本加锁的逻辑源码

目标代码

//通过getLock获取RLock对象,进行加锁
RLock lock = redissonClient.getLock("serviceKey");
lock.lock();
//getLock源码 
@Override public RLock getLock(String name) {
  return new RedissonLock(connectionManager.getCommandExecutor(), name);
}

源码分析

public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
    super(commandExecutor, name);
    //命令执行器,封装了redis的链接,用于操作redis的命令
    this.commandExecutor = commandExecutor;
    this.id = commandExecutor.getConnectionManager().getId();
    //看门狗续期时间(重点)
    this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
    this.entryName = id + ":" + name;
}

  看门狗相关的续期时间配置

public class Config {

     ......
    //看门狗默认续期时间是30s
    private long lockWatchdogTimeout = 30 * 1000;

}

  加锁相关的代码

public class RedissonLock extends RedissonExpirable implements RLock{
    @Override
    public void lock() {
        try {
            //参数1.续期时间
            //参数2.时间单位
            //参数3.加锁过程是否允许中断
            lock(-1, null, false);
        } catch (InterruptedException e) {
            throw new IllegalStateException();
        }
    }

    @Override
    public void lock(long leaseTime, TimeUnit unit) {
        try {
            lock(leaseTime, unit, false);
        } catch (InterruptedException e) {
            throw new IllegalStateException();
        }
    }
}

  接下来加锁流程会执行到 RedissonLock.tryLockInnerAsync() 在该方法中,执行加锁的lua脚本代码如下

    //如果不存在加锁的key(serviceKey)就执行加锁的逻辑
"if (redis.call('exists', KEYS[1]) == 0) then " +
    //hincrby命令:给指定Hash类型Key对应的Map结构的,key对应的value值进行+1
    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
    //pexpire命令:给指定的Key设置过期时间
    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
    "return nil; " +
    "end; " +
    //判断Hash类型的加锁的key对应的的Map结构中,key对应的value是否存在
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
    //将key对应的value值进行+1
    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
    //pexpire命令:给指定的Key设置过期时间
    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
     "return nil; " +
     "end; " +
    //返回Key(serviceKey)的过期时间
"return redis.call('pttl', KEYS[1]);",

   RedissonLock.tryLockInnerAsync() 在该方法中,执行了lus脚本后,内部在调用,evalWriteAsync方法中传入了  internalLockLeaseTime 也就是上文介绍的看门狗锁续期的时间,

        这里可以大胆猜测一下,当线程持有锁的时间超时后,如果线程任务仍然没有执行完,会触发看门狗的锁续期操作

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.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }

  RFuture中返回的NodeSource对象,

        NodeSource对象是封装了,加锁的Key对应的Redis  Master节点/Slave节点 相关的信息

    @Override
    public <T, R> RFuture<R> evalWriteAsync(String key, Codec codec, RedisCommand<T> evalCommandType, String script, List<Object> keys, Object... params) {
        NodeSource source = getNodeSource(key);
        return evalAsync(source, false, codec, evalCommandType, script, keys, params);
    }

  NodeSource对象封装的相关参数如下

    private NodeSource getNodeSource(String key) {
        //计算当前Key所在槽的值
        //如果是cluster模式,则使用crc16算法计算
        //如果是主从模式,则使用固定的槽的值
        int slot = connectionManager.calcSlot(key);
        //通过计算得到的槽的值,获取到当前加锁的Redis节点实例信息
        MasterSlaveEntry entry = connectionManager.getEntry(slot);
        return new NodeSource(entry, slot);
    }

  槽的计算源码

    @Override
    public int calcSlot(String key) {
        if (key == null) {
            return 0;
        }

        int start = key.indexOf('{');
        if (start != -1) {
            int end = key.indexOf('}');
            key = key.substring(start+1, end);
        }
        //MAX_SLOT =16384 
        //将Key,CRC16计算出来的值 对16384取模
        int result = CRC16.crc16(key.getBytes()) % MAX_SLOT;
        log.debug("slot {} for {}", result, key);
        return result;
    }

 Redisson锁数据结构

  参考.https://www.bianchengquan.com/article/570298.html

  Redisson存储锁的数据类型是Hash

  Hash类型的key包含了当前线程的信息

      

 

   

  这里表面数据类型是Hash类型,Hash类型相当于我们java的 <key,<key1,value>> 类型,这里key是指 'redisson'

  它的有效期还有9秒,我们再来看里们的key1值为 078e44a3-5f95-4e24-b6aa-80684655a15a:45它的组成是:

  guid + 当前线程的ID。后面的value是就和可重入加锁有关。

 

 

问题

  1、如果在某个机器上的某个线程,已经对key加锁了,那么这台机器上的其他线程如果尝试去对key加锁,会怎么样?肯定是会阻塞住,那么他是如何阻塞住的?

  2、如果在某个机器上的某个线程,已经对key加锁了,那么其他机器上的某个线程尝试去对那个key加锁,会怎么样?肯定也是会阻塞住的,那么他是如何阻塞住的呢?

 


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Redis分布式锁—SETNX+Lua脚本实现篇发布时间:2022-07-22
下一篇:
Lua行为树实现发布时间:2022-07-22
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap