首页 > 数据库 >Redis分布式锁(Redisson)

Redis分布式锁(Redisson)

时间:2023-03-04 22:45:12浏览次数:34  
标签:Redisson KEYS Redis threadId redis ARGV call time 分布式

Redis分布式锁(Redisson)

什么是Redission呢

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。

Redission提供了分布式锁的多种多样的功能

image-20230304220532482

加锁原理

image-20230304221328073

加锁主要是这段lua脚本

"if (redis.call('exists', KEYS[1]) == 0) then " + //判断是否存在锁
        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + //不存在,则获取锁,hash结构
        "redis.call('pexpire', KEYS[1], ARGV[1]); " + //设置过期时间为ARGV[1]
        "return nil; " +
        "end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + //如果第二个客户端来获取锁,判断是否存在这个客户端ID ARGV[2]为客户端id
        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
        "return nil; " +
        "end; " +
"return redis.call('pttl', KEYS[1]);" //加锁失败,就会返回当前锁的存活时间 pttl

KEYS[1] : 锁名称

ARGV[1]: 锁失效时间

ARGV[2]: id + ":" + threadId; 锁的key

exists: 判断数据是否存在 name:是lock是否存在,如果==0,就表示当前这把锁不存在

redis.call('hset', KEYS[1], ARGV[2], 1);此时他就开始往redis里边去写数据 ,写成一个hash结构

Lock{

​ id + ":" + threadId : 1

}

如果当前这把锁存在,则第一个条件不满足,再判断

redis.call('hexists', KEYS[1], ARGV[2]) == 1

此时需要通过大key+小key判断当前这把锁是否是属于自己的,如果是自己的,则进行

redis.call('hincrby', KEYS[1], ARGV[2], 1)

将当前这个锁的value进行+1 ,redis.call('pexpire', KEYS[1], ARGV[1]); 然后再对其设置过期时间,如果以上两个条件都不满足,则表示当前这把锁抢锁失败,最后返回pttl,即为当前这把锁的失效时间。

锁的互斥机制

此时,如果客户端 2 来尝试加锁,会如何呢?首先,第一个 if 判断会执行 exists myLock,发现 myLock 这个锁 key 已经存在了。接着第二个 if 判断,判断一下,myLock 锁 key 的 hash 数据结构中,是否包含线程 2 的 ID,这里明显不是,因为那里包含的是线程 1 的 ID。所以,线程2 会执行:

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

返回的一个数字,这个数字代表了 myLock 这个锁 key 的剩余生存时间。

Redissson tryLock 的主流程:

 @Override
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        // 获取锁等待时间
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        long threadId = Thread.currentThread().getId();
        // 1.尝试获取锁
        Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
        // 2. 获取锁成功 lock acquired
        if (ttl == null) {
            return true;
        }
        
        // 申请锁的耗时如果大于等于最大等待时间,则申请锁失败.
        time -= System.currentTimeMillis() - current;
        if (time <= 0) {
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
        
        //记录当前时间
        current = System.currentTimeMillis();
        
        /**
         * 2.订阅锁释放事件,并通过 await 方法阻塞等待锁释放,有效的解决了无效的锁申请浪费资源的问题:
         * 基于信息量,当锁被其它资源占用时,当前线程通过 Redis 的 channel 订阅锁的释放事件,一旦锁释放会发消息通知待等待的线程进行竞争.
         *
         * 当 this.await 返回 false,说明等待时间已经超出获取锁最大等待时间,取消订阅并返回获取锁失败.
         * 当 this.await 返回 true,进入循环尝试获取锁.
         */
        RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
         // await 方法内部是用 CountDownLatch 来实现阻塞,获取 subscribe 异步执行的结果(应用了 Netty 的 Future)
        if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
            if (!subscribeFuture.cancel(false)) {
                subscribeFuture.onComplete((res, e) -> {
                    if (e == null) {
                        unsubscribe(subscribeFuture, threadId);
                    }
                });
            }
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
		// 计算获取锁的总耗时,如果大于等于最大等待时间,则获取锁失败.
        try {
            time -= System.currentTimeMillis() - current;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }
        	
            /**
             * 3.收到锁释放的信号后,在最大等待时间之内,循环一次接着一次的尝试获取锁
             * 获取锁成功,则立马返回 true,
             * 若在最大等待时间之内还没获取到锁,则认为获取锁失败,返回 false 结束循环
             */
            while (true) {
                long currentTime = System.currentTimeMillis();
                ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    return true;
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(waitTime, unit, threadId);
                    return false;
                }

                // waiting for message
                currentTime = System.currentTimeMillis();
                if (ttl >= 0 && ttl < time) {
                    subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(waitTime, unit, threadId);
                    return false;
                }
            }
        } finally {
            unsubscribe(subscribeFuture, threadId);
        }
//        return get(tryLockAsync(waitTime, leaseTime, unit));
    }

标签:Redisson,KEYS,Redis,threadId,redis,ARGV,call,time,分布式
From: https://www.cnblogs.com/chengbb/p/17179408.html

相关文章

  • 在 .NET Core 中使用 Redis 创建分布式锁
    .NETCoreRedis分布式锁,在本文中,我们将讨论如何在.NETCore中使用Redis创建分布式锁。我们在构建分布式系统的时候,会面临多个进程共同处理一个共享资源,由于一次只能......
  • lua脚本在redis中的使用
    先开启redis的日志输出修改redis.conf文件,设置logfile/root/tools/redis-6.0.9/logs/redis.log重启redissystemctlrestartredisd创建一个简单的lua脚本test.......
  • 【Redis场景5】集群秒杀优化-分布式锁
    集群环境下的秒杀问题前序【Redis场景1】用户登录注册【Redis场景2】缓存更新策略(双写一致)【Redis场景3】缓存穿透、击穿问题【Redis场景拓展】秒杀问题-全局唯一ID......
  • 自实现分布式链路追踪 方案&实践
    前言:排查问题是程序员的基本能力也是必须要会的,在开发环境,我们可以debug,但是一旦到了服务器上,就很难debug了,最有效的方式就是通过日志揪出bug,而一次请求的日志如果没有一个......
  • 分布式系统中的网络分区问题
    网络分区问题什么是网络分区?网络分区只在分布式集群中,节点之间由于网络不通,导致集群中节点形成不同的子集,子集中节点之间网络互通,而子集与子集之间网络不通。如何判断是......
  • redis cluster 部署
    rediscluster部署服务器说明192.168.2.200:7000...192.168.2.200:7005创建集群目录mkdircluster-testcdcluster-testmkdir7000700170027003700470......
  • Windows系统安装Redis服务
    网上有很多的安装教程,但Redis版本都比较老,最近从github上搜索到最新版本,验证可用,记录一下1、应用下载地址:https://github.com/zkteco-home/redis-windows2、选择对应得ta......
  • 【redis】配置优化及从库优先级
    https://blog.51cto.com/u_15902893/5912902 vim/etc/redis/redis.conf##################基础#################################daemonizeyes//是否以守护进程......
  • Redis分布式锁常见坑点分析
    日常开发中,基于Redis天然支持分布式锁,大家在线上分布式项目中都使用过Redis锁。本文主要针对日常开发中加锁过程中某些异常场景进行讲解与分析。本文讲解示例代码都在......
  • 【Redis的三种数据删除策略】定时定期惰性,超出内存就自动清理
    https://blog.csdn.net/DQWERww/article/details/126453008https://blog.csdn.net/qq_38056518/article/details/122107638  内存淘汰机制Redis有过期策略......