Redisson可重入锁原理

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Redisson提供的分布式锁是可重入的,它使用的是Hset命令和Hash数据结构。

一、Redisson可重入锁原理

1、可重入:利用hash结构记录线程id和重入次数。

2、可重试:利用信号量和PubSub功能实现等待、唤醒,获取

锁失败的重试机制。

3、超时续约:利用watchDog,每隔一段时间(releaseTime

/3),重置超时时间。
640 (4).png
调用redisson.lock()方法会在redis中存储一个hash数据结构,key为锁的名称,value中的field为当前操作的线程id,value为锁重入的次数。

640 (5).png
代码测试:


private  RLock rLock=null;
    @ApiOperation(value="测试分布式锁的可重入锁原理", notes="testRedisson02")
    @GetMapping("/testRedisson02")
    public String testRedisson02() throws InterruptedException {
        //1、获取锁(可重入),并指定锁的名称
        rLock=redissonClient.getLock("lock:testRedisson02");
        //2、尝试获取锁,参数分别是:waitTime:获取锁的最大等待时间(期间会重试)
        // leaseTime:锁自动释放时间  TimeUnit:时间单位
        //tryLock(long waitTime, long leaseTime, TimeUnit unit)
        boolean isLock=rLock.tryLock(1,100, TimeUnit.SECONDS);
        //3、判断锁获取成功及释放
        if(isLock){
            try {
                log.info("执行正常的业务02......");
                this.testRedisson03();
            }catch (Exception e){
                log.info("锁获取异常02e:"+e);
            }finally {
                //锁未关闭,则手动释放锁
                if(rLock.isLocked()){
                    rLock.unlock();
                    log.info("释放锁成功02");
                }
            }
        }
        return "true";
    }

    public void testRedisson03() throws InterruptedException {
        //2、尝试获取锁,参数分别是:waitTime:获取锁的最大等待时间(期间会重试)
        // leaseTime:锁自动释放时间  TimeUnit:时间单位
        //tryLock(long waitTime, long leaseTime, TimeUnit unit)
        boolean isLock=rLock.tryLock(1,100, TimeUnit.SECONDS);
        //3、判断锁获取成功及释放
        if(isLock){
            try {
                log.info("执行正常的业务03......");
            }catch (Exception e){
                log.info("锁获取异常03e:"+e);
            }finally {
                //锁未关闭,则手动释放锁
                if(rLock.isLocked()){
                    rLock.unlock();
                    log.info("释放锁成功03");
                }
            }
        }
    }

输出结果:
640 (6).png
具体分析如下:
640 (7).png
在上面的testRedisson02方法中,当外层testRedisson02方法加锁之后,会获取当前的线程标识存入field字段,并将value+1;当内层testRedisson03方法再次加这个锁,会先判断当前线程与field中存的线程是否是一样的,如果是一样的,value+1;此时value为2。如果要解锁,先要判断锁是否是自己的(比对key和field字段),如果是,则value-1。
640 (8).png
获取锁的Lua脚本源码

return 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]);",

释放锁的Lua脚本源码:

return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                        "return nil;" +
                        "end; " +
                        "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                        "if (counter > 0) then " +
                        "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                        "return 0; " +
                        "else " +
                        "redis.call('del', KEYS[1]); " +
                        "redis.call('publish', KEYS[2], ARGV[1]); " +
                        "return 1; " +
                        "end; " +
                        "return nil;",

二、总结
640 (9).png
1、不可重入Redis分布式锁:

原理:利用setnx的互斥性;利用ex避免死锁;释放锁时判

断线程标识。

缺陷:不可重入、无法重试、锁超时失效。

2、可重入的Redis分布式锁:

原理:利用hash结构,记录线程标示和重入次数;利用

watchDog延续锁时间;利用信号量控制锁重试等待。

缺陷:redis宕机引起锁失效问题。--主从一致性问题

3、RedissonmultiLock:

原理:多个独立的Redis节点,必须在所有节点都获取重入

锁,才算获取锁成功。

缺陷:运维成本高、实现复杂。

更多详细资料,请关注个人微信公众号或搜索“程序猿小杨”添加。

相关文章
|
5月前
|
Java API 微服务
Java 21 与 Spring Boot 3.2 微服务开发从入门到精通实操指南
《Java 21与Spring Boot 3.2微服务开发实践》摘要: 本文基于Java 21和Spring Boot 3.2最新特性,通过完整代码示例展示了微服务开发全流程。主要内容包括:1) 使用Spring Initializr初始化项目,集成Web、JPA、H2等组件;2) 配置虚拟线程支持高并发;3) 采用记录类优化DTO设计;4) 实现JPA Repository与Stream API数据访问;5) 服务层整合虚拟线程异步处理和结构化并发;6) 构建RESTful API并使用Springdoc生成文档。文中特别演示了虚拟线程配置(@Async)和StructuredTaskSco
652 0
|
存储 SQL 关系型数据库
MySQL高级篇——索引失效的11种情况
索引优化思路、要尽量满足全值匹配、最佳左前缀法则、主键插入顺序尽量自增、计算、函数导致索引失效、类型转换(手动或自动)导致索引失效、范围条件右边的列索引失效、不等于符号导致索引失效、is not null、not like无法使用索引、左模糊查询导致索引失效、“OR”前后存在非索引列,导致索引失效、不同字符集导致索引失败,建议utf8mb4
MySQL高级篇——索引失效的11种情况
|
Java 数据库连接 数据库
mybatis查询数据,返回的对象少了一个字段
mybatis查询数据,返回的对象少了一个字段
1054 9
|
Java 编译器
有关电脑中idea编译报错问题java: No implementation was created for AdminUserConverter due to having a problem in
有关电脑中idea编译报错问题java: No implementation was created for AdminUserConverter due to having a problem in
1127 0
|
NoSQL 前端开发 Java
【幂等性大坑】事务提交前释放锁导致锁失效问题
在事务中,使用了 Redis 分布式锁.这个方法一旦执行,事务生效,接着就 Redis 分布式锁生效,代码执行完后,先释放 Redis 分布式锁,然后再提交事务数据,最后事务结束。如果是表单重复提交场景,可以尝试给“订单号”等有唯一性的字段加唯一索引,这样重复提交时会因为唯一索引约束导致索引失效。5、如果表的一个字段,要作为另外一个表的外键,这个字段必须有唯一约束(或是主键),如果只是有唯一索引,就会报错。2、创建唯一约束,会自动创建一个同名的唯一索引,该索引不能单独删除,删除约束会自动删除索引。
【幂等性大坑】事务提交前释放锁导致锁失效问题
|
NoSQL 算法 Redis
Redis集群哈希槽数据分片
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽. 集群的每个节点负责一部分hash槽。这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。
500 0
Redis集群哈希槽数据分片
Redisson 分布式锁的正确使用
你会正确使用分布式锁吗?
2510 0
Redisson 分布式锁的正确使用
|
Java 测试技术 API
技术笔记:UML的9种图例解析(转)
技术笔记:UML的9种图例解析(转)
|
消息中间件 Kafka RocketMQ
Kafka重平衡机制
当集群中有新成员加入,或者某些主题增加了分区之后,消费者是怎么进行重新分配分区再进行消费的?这里就涉及到重平衡(Rebalance)的概念,下面我就给大家讲解一下什么是 Kafka 重平衡机制,我尽量做到图文并茂通俗易懂。
1889 0
Kafka重平衡机制
|
运维 NoSQL Java
【Redis】6、Redisson 分布式锁的简单使用(可重入、重试机制...)
【Redis】6、Redisson 分布式锁的简单使用(可重入、重试机制...)
985 1