当前位置: 首页 > article >正文

【redisson】redisson分布式锁原理分析

为什么要有分布式锁 分布式锁是基于什么延伸出来的?这个可以在博主首页搜索:超卖, 里面通过超卖例子说明分布式锁和传统锁的区别,本文重点阐述的是redisson的api使用及其源码分析。

文章目录

  • springboot整合redisson
  • redisson三个重要参数
    • leaseTime
    • waitTime
    • lockWatchdogTimeout
  • redisson常用api
  • api源码简析
    • leaseTime和watchDog不能共存的原因
    • lock同步阻塞的原因

springboot整合redisson

    <!-- redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.16.5</version>
        </dependency>


@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        // 创建 Redisson 配置
        Config config = new Config();
        SingleServerConfig singleServerConfig = config.useSingleServer();
        // 配置 Redis 连接地址(假设是单机版 Redis)
        singleServerConfig.setAddress("redis://xx.xx.xx.xxx:6379");
        singleServerConfig.setDatabase(1);
        singleServerConfig.setPassword("xxxxxxx");

        // 创建并返回 RedissonClient
        return Redisson.create(config);
    }


    /**
     * 哨兵模式配置
     */
//    @Bean
//    public RedissonClient createClient() {
//        // 创建配置对象
//        Config config = new Config();
//
//        // 使用 JSON 编解码器
//        config.setCodec(new JsonJacksonCodec());
//
//        // 配置哨兵模式
//        SentinelServersConfig sentinelServersConfig = config.useSentinelServers();
//        // 配置哨兵地址,多个哨兵地址之间用逗号分隔
//        sentinelServersConfig.setSentinelAddresses(Arrays.asList(
//                "redis://xx.xx.xx.xxx:26379", "redis://xx.xx.xx.xxx:26380","redis://xx.xx.xx.xxx:26381"
//        ));
//
//        // 配置主 Redis 实例名称
//        sentinelServersConfig.setMasterName("mymaster");
//
//        // 配置数据库索引(Redis 默认是 0)
//        sentinelServersConfig.setDatabase(0);
//
//        // 配置哨兵认证密码
//        sentinelServersConfig.setPassword("xxxxx");
//
//        /**
//         * 设置连接池的最大连接数、最小连接数等参数(可选)
//         */
//        // 设置连接超时为 10 秒
//        sentinelServersConfig.setConnectTimeout(10000);
//        // 设置操作超时为 3 秒
//        sentinelServersConfig.setTimeout(3000);
//        // 设置重试次数
//        sentinelServersConfig.setRetryAttempts(3);
//        // 设置重试间隔为 1.5 秒
//        sentinelServersConfig.setRetryInterval(1500);
//
//        // 返回 Redisson 客户端
//        return Redisson.create(config);
//    }

}

redisson三个重要参数

leaseTime

  1. leaseTime
    锁自动释放时间(或者理解为锁持有时间,持有30s和30s释放是一个意思),当出现异常 或者没手动释放锁,在到了自动释放时间时,会自动将锁释放

waitTime

  1. waitTime
    等待重试时间,有点像java原生定时器的延迟执行时间,当前线程发现锁已经被其它线程持有了(当前线程获取锁失败,通俗点解释就是 代码已经被上锁了 资源被占用了), 是应该直接重试呢 还是等待一段时间重试呢? 这种选择就是通过waitTime来控制的,当waitTime = 0时,那当前线程会马上重新尝试获取锁;如果waitTime为不为0 例如为30s, 那么当前线程会在30s再重试 尝试获取锁,如果获取成功则继续执行。

    由此也会延伸出一个疑问:如果waitTime时间设置过长或过短 会对锁造成什么影响吗?
    答案是否定的,不会造成死锁,在一般的项目中waitTime只要不是太不合理都问题不大;
    那我们严格分析一下,在高并发的项目中会发生什么:
    如果waitTime过短,那它会多次尝试 造成不必要的资源损耗 ;
    那如果waitTime > leaseTime (假设实际释放时间 = leaseTime )呢? 那可能等着等着已经被其它线程占用了
    (虽然是lua脚本,但是只是脚本执行的时候是原子性的!比如一个脚本设置过期时间为1h 不是这1h原子性操作 而是执行脚本一瞬间是原子操作)

lockWatchdogTimeout

  1. watchDog看门狗,注意是和leaseTime互斥的!可以简单理解为:如果都强制说明了锁释放时间,那么也就不需要锁续命机制了,watchDog的默认时间是30秒,可以在配置Redisson的时候 Config里面自行修改;

    看门狗主要解决的是 我们可能没法很好的评估线程执行完的时间,声明时间很可能导致锁提前释放;redisson的watchDog机制 发现线程没结束 会锁续命,保证线程完全处于加锁状态中。

    这里博主曾陷入一个误区,如果业务始终不完成 例如死循环 锁岂不是一直不释放 就成了死锁?
    答案虽然是肯定的,但是这不是我们该考虑的范畴,业务死循环 有没有锁 都会OOM的 这不应该是我们写出来的业务代码,应该从源头去避免这个问题。

redisson常用api

  1. lock()
    lock是阻塞锁,没获取到锁会一直阻塞等待:

     线程1加锁 ->
     线程2阻塞等待 ->
     线程1释放锁 ->
     线程2加锁 ->
     线程2释放锁
    
  2. tryLock()
    tryLock是异步锁,如果发现获取锁失败 会直接返回false,我们可以手动判断加锁结果 做不同的业务 (比如false中直接return表示不等待 视为失败)

    以上两个api默认都是有watchDog机制的 ,因为没有显式的指定leaseTime。

  3. tryLock(long waitTime, long leaseTime, TimeUnit unit)
    有严谨的同学,希望完全自己掌控时间(我命由我 不由框架),会选择自己定义锁释放的时间,但是定义了leaseTime,watchDog机制就失效了。

    缺点:可能会和延迟双删一样的问题了,锁可能会提前释放,也可能预估不准确。
    优点:保证锁在一定时间内会释放 不会阻塞。

api源码简析

leaseTime和watchDog不能共存的原因

不管是lock还是tryLock,我们不显式声明leaseTime的时候,它内部都是声明-1,
在这里插入图片描述
主要是在tryAcquire方法里面判断:
在这里插入图片描述

当配置了leaseTime,则以leaseTime为准,如果没配置 ,则会使用watchDog的时间,且会不断续命(并不是只用一次watchDog的时间就释放锁)
在这里插入图片描述

我们也可以用demo来验证一下:

锁没有提前释放例子
在这里插入图片描述

声明了leaseTime ,watchDog失效的例子

当我们声明leaseTime = 10s , 在10s就会自动将锁释放,等到我们业务代码执行30s后,再去手动释放锁会提示unlock失败 并报错: attempt to unlock lock, not locked by current thread by node id xxx

所以需要在实际业务中,我们手动释放锁要加一个判断
if (lock.isHeldByCurrentThread())
否则会抛异常在这里插入图片描述

(为什么有了自动释放 还需要手动释放呢? 还是那句话 我命由我,自动的时间可能会受一些意想不到的因素影响,例如网络、jvm的gc导致的stw)

lock同步阻塞的原因

lock源码 朴实无华的同步方法
在这里插入图片描述

我们可以对比一下tryLock是怎么做的:
在这里插入图片描述
只看方法名可能不太够,我们稍微追踪一下源码(过程比较简单 就不一一贴出来了) 追踪几步后可以看到异步执行的代码:
在这里插入图片描述


http://www.kler.cn/a/467503.html

相关文章:

  • CSS 学习之正确看待 CSS 世界里的 margin 合并
  • 计算机网络 (28)虚拟专用网VPN
  • pip error: microsoft visual c++ 14.0 or greater is required
  • 在Spring Boot项目中使用Zookeeper和Curator实现高效、可靠的分布式锁
  • 【网络协议】开放式最短路径优先协议OSPF详解(一)
  • 【数据结构】链表(2):双向链表和双向循环链表
  • 【深度学习】交叉熵:从理论到实践
  • 专业140+总分400+中国海洋大学819信号与系统考研综合考研经验中海大电子信息与通信工程,真题,。大纲,参考书。
  • 【go类库分享】go rate 原生标准限速库
  • 旷视科技Java面试题及参考答案
  • AWS IAM基础知识
  • ‘元素.style.样式名‘获取不到样式,应该使用Window.getComputedStyle()获取正真的样式
  • 什么是 AJAX ?
  • CentOS7 使用yum报错:[Errno 14] HTTP Error 404 - Not Found 正在尝试其它镜像。
  • 【VScode】设置代理,通过代理连接服务器
  • 【CVPR 2024】【遥感目标检测】Poly Kernel Inception Network for Remote Sensing Detection
  • 【2025软考高级架构师】案例题重点知识——第三部分
  • 反直觉导致卡关-迫击炮谜题
  • unity学习4:git和SVN的使用差别
  • PHP语言的计算机基础
  • 探索最新的编程技术趋势:AI 编程助手和未来的编程方式
  • 瑞吉外卖项目学习笔记(十一)分页查询订单列表
  • 学习随笔:word2vec在win11 vs2022下编译、测试运行
  • CSP初赛知识学习计划
  • Spring Cloud Security集成JWT 快速入门Demo
  • kafka使用以及基于zookeeper集群搭建集群环境