首页 > 数据库 >redisson分布式锁原理

redisson分布式锁原理

时间:2024-05-03 17:22:34浏览次数:27  
标签:加锁 过期 解锁 获取 redisson 线程 原理 分布式

参考:

图灵课堂

 https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95

https://blog.csdn.net/asd051377305/article/details/108384490

分布式锁的引入

当在单机单线程情况下,是不用考虑任何并发问题的,一切都是那么的美好,那么的顺其自然。

在单机多线程情况下,就要考虑并发的问题,但是不慌,因为有JUC包提供的功能十分齐全的锁机制,可以给我们的代码保驾护航,免了我们的后顾之忧。

但是随着业务量的增长,分布式越来越多的出现在我们的项目代码中,再使用原始的JUC锁,就有些力不从心了,要进行技术选择的升级。

如果还是使用单机的JUC锁,那么就是在自己的节点上加锁,但是真正要操作的数据是多个节点机器并存的,那么就一定会出现重复操作的现象,这种问题是必须要避免的。例如秒杀情况下的超卖问题,此时请求流量突增,如果没有进行相关锁处理,一定会出现超卖问题,这个是一定要规避的。

1. 初始阶段,使用redis实现分布式锁,setnx key value;这样的命令是如果没有就设置值,如果有就不进行操作;返回值是integer类型;1:成功;0:失败;最后执行完因为代码后删除这个key就可以了;

2. 但是上面的还是有问题,如果刚加完锁,节点宕机,重启之后,去redis恢复数据,这个锁还是在的,那么别的就无法加锁了,造成了死锁问题;此时改进就是加一个超时时间,如果是分开操作,就是非原子操作,就可能还存在无法释放锁的问题,所以要在同一条命令中执行加锁和加超时时间。

在Redis中,SETNX(SET if Not Exists)命令本身并不直接支持设置超时时间(过期时间)。SETNX命令仅用于在键不存在时设置其值,如果键已存在,则不做任何操作。

要设置键的超时时间(过期时间),你需要使用EXPIRE命令或SET命令的扩展选项。但请注意,SETNX和EXPIRE是两个独立的命令,你需要分别执行它们。

然而,如果你想要在一个原子操作中实现SETNX并设置超时时间,你可以使用Redis的SET命令的扩展选项,如EX(以秒为单位设置键的过期时间)和NX(仅当键不存在时设置键的值)。以下是一个示例:
SET mykey myvalue EX 10 NX
这个命令会在mykey不存在时设置其值为myvalue,并设置其过期时间为10秒。如果mykey已经存在,则命令不会执行任何操作。

上面的这个操作是原子性的,可以保证加锁的同时加上锁超时时间。

3. 但是这样的就好了吗?不是,因为锁加上去了,但是有可能会出现别的线程来了释放锁,锁也没有带有标识,就可以会出现线程1加锁,线程2来了解锁,这样相当于没有加锁。

4. 那么就可以针对这样的情况进行优化,加上一个标识,如何在锁释放之前先判断一下是不是我这个线程加的锁,如果是那么就释放,如果不是就不释放。但是这里还是要注意一点,释放锁是不是原子性的,如果是分开写了两行,那么就不是原子性了,这个要注意。这块可以使用lua脚本来实现。

5. 还有一个情况就是,如果我突然之间执行时间增长,但是锁过期时间不变,那么到期就主动释放锁了,此时还是类似没有加锁,那么 还是有并发问题。

上面就是分布式锁的一些场景,需要多考虑。

分布式锁需满足四个条件

首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  3. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了,即不能误解锁。
  4. 具有容错性。只要大多数Redis节点正常运行,客户端就能够获取和释放锁。

锁,归根到底还是要加锁,解锁,只是要保证原子性。redisson就很好的保证了加锁解锁的原子性,是通过lua脚本来实现的,并且也使用了发布订阅功能来唤醒等待的线程。

Redisson分布式原理

 

redisson锁默认是非公平锁。

首先是要保证加锁解锁都是原子性的;然后还有一个就是加锁解锁都是同一个线程;然后就是锁不能在没有执行完业务代码之前就失效,要能够进行锁续命;然后别的没有获取到锁的线程要尝试获取锁,这个过程不能太频繁,要注意性能;并且当持有锁的线程释放锁之后要能令这些等待的线程能够获取到锁。

加锁和解锁是要一一对应的,否则会出现死锁的情况。

// 1.构造redisson实现分布式锁必要的Config
Config config = new Config();
config.useSingleServer().setAddress("IP地址加端口号").setPassword("密码").setDatabase(0);
// 2.构造RedissonClient
RedissonClient redissonClient = Redisson.create(config);
// 3.获取锁对象实例(无法保证是按线程的顺序获取到)
RLock rLock = redissonClient.getLock(lockKey);
try {
    /**
     * 4.尝试获取锁
     * waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败
     * leaseTime   锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)
     */
    boolean res = rLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);
    if (res) {
        //成功获得锁,在这里处理业务
    }
} catch (Exception e) {
    throw new RuntimeException("aquire lock fail");
}finally{
    //无论如何, 最后都要解锁
    rLock.unlock();
}

注意:加锁和解锁都用到了lua脚本,lua可以保证原子性。

加锁

多个线程并发请求,调用getLock方法,某个线程获取到锁之后,别的线程就无法再获取锁,就进入一个等待队列。

获取到锁的线程:会执行业务逻辑。其中会启动一个异步后台线程去定时监控当前线程是否执行完毕,如果没有执行完毕,就进行过期时间的续期,默认是30S,是可以修改这个默认值的。这个是形象的看门口机制,是通过异步线程,并且是循环调用来实现的,组件中这样的设置很常见。

加锁的方法,先判断是否加锁了,如果没有锁,就加锁,设置过期时间,并且设置一个重入次数加1;返回null。

如果有锁了,并且是当前线程的,那么证明是锁重入场景,锁重入次数加1,重新设置过期时间;返回过期时间ttl。

如果有锁了,并且不是当前线程,就返回锁过期时间ttl。

返回null证明是加锁成功了,返回过期时间是让进入队列的竞争线程过去这么久之后进行尝试获取锁,以为如果一直频繁的自旋去尝试获取锁,对CPU来说是压力很大的,这个要多注意。

解锁:

如果锁不存在,那么就证明获取锁的线程已经执行完毕,所以释放过锁了;可能是以为宕机等原因没有来得发布消息告诉别的线程去抢锁,所以要发布一个消息;发布这个消息是为了令阻塞等待的线程去争抢锁。这里还用到了redis的消息的发布订阅功能。返回1.

如果锁存在,但是锁标识不一致,证明不是当前占用锁的线程,无法释放锁,无法执行后续逻辑。不允许释放非当前线程加的锁。返回null。

如果锁存在,并且锁标识一致,那么就将重入次数减一。

  如果减一之后重入次数不为0,证明还有别的锁未释放,不能释放锁,重新设置过期时间,返回0;

  如果减一之后重入次数未0,证明可以释放锁了,此时可以直接del掉锁,然后发布一个消息,通知等待的线程去争抢锁。返回1.

主从架构锁失效问题

因为为了保证高可用,我们一般都是要真的集群加从节点,用来保证数据的完整性。如果在master节点加锁成功,还没有来得及同步到从节点就宕机了,这个锁就失效了,还可能有锁并发问题。

CAP机制解析zk和redis分布式锁的区别

zk是CP架构的,就是保证了分区容错性和一致性,保证一致性的技术手段是半数机制原理,就是数据要存储入半数以上的节点才会认为是存储成功,否则就会认为存储失败,回滚插入操作。可以看成是同步的,必须要半数以上的写入成功才会认为是成功。

redis的分布式锁是保证AP,就是分区容错性和高可用性,高可用性是主节点接收到请求之后就返回写入成功,可以认为是一个异步的,后面异步进行从节点复制,但是可能会丢失部分数据,但是保证了高可用性。

高可用和一致性是天然相悖的,这个要看业务场景如何去均衡了。

redlock分布式锁原理和问题 

标签:加锁,过期,解锁,获取,redisson,线程,原理,分布式
From: https://www.cnblogs.com/0630sun/p/18170476

相关文章

  • Oracle之数据库一致性读的原理
    1.概述在Oracle数据库中,undo主要有三大作用:提供一致性读(ConsistentRead)、回滚事务(RollbackTransaction)以及实例恢复(InstanceRecovery)。2.原理一致性读是相对于脏读(DirtyRead)而言的。假设某个表T中有10000条记录,获取所有记录需要15分钟时间。当前时间为9点整,某用户A发出一条......
  • 《深度学习原理与Pytorch实战》(第二版)
    第1章深度学习简介深度学习——利用深度人工神经网络来进行自动分类、预测和学习的技术,深度学习=深度人工神经网络超过三层的神经网络都可以叫做深度神经网络人工神经网络的关键算法——反向传播算法深度网络架构,即整个网络体系的构建方式和拓扑连接结构,主要分为3种:......
  • 原子操作的实现原理与使用-03
    所谓“原子操作”就是这个操作不会被打断。Linux有2种原子操作:原子变量、原子位。 原子变量的内核操作函数原子变量的操作函数在Linux内核文件arch\arm\include\asm\atomic.h中。原子变量类型如下,实际上就是一个结构体(内核文件include/linux/types.h):  原子变量的内......
  • 【Netty】【XXL-JOB】时间轮的原理以及应用分析
    1 前言今天晚上看了一本70多页的讲解时间轮的PDF,从是什么为什么以及原理到源码中的应用分析,讲的真好。这节我就按我理解的思路捋一下,记录一下哈。2 时间轮概述2.1 时间轮是什么时间轮是一种高效利用线程资源进行批量化调度的一种调度模型。把大批量的调度任务全部绑......
  • 蚂蚁面试:Springcloud核心组件的底层原理,你知道多少?
    文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录博客园版为您奉上珍贵的学习资源:免费赠送:《尼恩Java面试宝典》持续更新+史上最全+面试必备2000页+面试必备+大厂必备+涨薪必备免费赠送:《尼恩技术圣经+高并发系列PDF》,帮你实现技术自由,完成职业升级,薪......
  • 对于计算机微机结构及其工作原理的认识
    通过硬件组成角度谈谈我对于计算机微机的认识,计算机微机首先说其结构,其结构包括时钟、CPU、储存器以及IO接口。其中,时钟类似于一个开关和乐队的“指挥”,它控制着计算机程序运行的顺序,并且保持计算机多个程序并发执行的时间分配。而CPU则是最重要的组成部分了,它主要包括运算器、控......
  • redis集群原理
    由于redis主从,哨兵都有一些不便之处,redis就提出了集群的概念,并真正实现了。 在redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点的状态,如果master节点异常,则会做主从切换,将某一台slave作为master,哨兵的配置略微复杂,并且性能和高可用性等各方面表现一般......
  • Redis核心数据结构与高性能原理
    参考-图灵课堂-https://vip.tulingxueyuan.cnhttps://www.runoob.com/redis/redis-tutorial.html 常见得数据类型:Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sortedset:有序集合)。stringstring是redis最基本的类型,你可以理解成与Memcached一......
  • Redis 缓存/分布式锁/消息队列的应用
    缓存缓存是最常见的的应用类型,因为同等配置下,如果一台MySQL能支持上千的QPS,那么一台redis支持的QPS能达到上万,十倍于MySQL。客户端将热点数据存储在redis中,优先从redis读取数据,可以减轻数据库的访问压力。但将redis作为缓存,也存在一些问题,例如数据不一致。数据不一致场景:redis......
  • 微服务:分布式事务
    在分布式系统中,一个服务调用多个远程服务时,多个事务必须同时成功或失败。每一个服务的事务称为分支事务,整个业务称为全局事务 seata架构中有三个角色:TC事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚TM事务管理者:定义全局事务的范围、开始,提交,回滚全局事务RM资......