16.Redis系列之Redisson分布式锁原理

news2025/7/10 16:55:07

本文学习Redisson分布式锁的原理以及优缺点

1. Redisson分布式锁原理

lua脚本是原子操作,redis会将整个脚本作为一个整体执行,中间不会被其他命令打断

# RedissonLock.tryLockInnerAsync方法内lua脚本加锁
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return evalWriteAsync(getRawName(), 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(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
    }
1.1 加锁原理

1
2

如图与代码所示,sk_10001_stock_lock不存在时,加锁时会创建hash类型键sk_10001_stock_lock,并新增field为客户端id,value设置为1(hincrby key field 1不存在key则会创建它,并且设置field为1),并设置过期时间为60s

1.2 可重入原理

如lua脚本所示,当sk_10001_stock_lock存在客户端id时,会将客户端id的值+1,并且重新设置过期时间,所以保存客户端id就是为了可重入

1.3 锁互斥原理

如lua脚本所示,对于其他的客户端返回锁pttl过期时间,后续流程如代码所示

// RedissonLock.tryLock方法
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
        // ttl为空获取到锁
        if (ttl == null) {
            return true;
        }
        // 如果时间超过waitTime则获取锁失败
        time -= System.currentTimeMillis() - current;
        if (time <= 0) {
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
	// 订阅锁释放事件
        CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
        try {
            subscribeFuture.get(time, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
        } catch (ExecutionException e) {   
        }

        try {
            // 在最大等待时间内,循环获取锁,直到成功或失败
            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;
                }

                // 通过信号量semaphore共享锁阻塞等待
                currentTime = System.currentTimeMillis();
                if (ttl >= 0 && ttl < time) {
                   commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                }
            }
        } finally {
            // 取消订阅
            unsubscribe(commandExecutor.getNow(subscribeFuture), threadId);
        }
    }

1.4 锁续期原理
// RedissonLock.tryAcquireAsync方法
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {
            // 获取到锁
            if (ttlRemaining == null) {
                if (leaseTime > 0) {
                    internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
                    // 当leaseTime<=0时锁会自动续期
                    scheduleExpirationRenewal(threadId);
                }
            }
            return ttlRemaining;
        });
        return new CompletableFutureWrapper<>(f);
    }
 private void renewExpiration() {
        // Watch Dog机制,每10秒检查是否仍然持有锁,是则续期
        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
	    }
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        
        ee.setTimeout(task);
    }
    // 依次进入方法,最终找到RedissonBaseLock.renewExpirationAsync实现
    protected CompletionStage<Boolean> renewExpirationAsync(long threadId) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return 1; " +
                        "end; " +
                        "return 0;",
                Collections.singletonList(getRawName()),
                internalLockLeaseTime, getLockName(threadId));
    }

通过代码可知,当leasetime<=0时,锁通过Watch Dog机制[其实就是一个后台定时任务线程]每10秒检查是否持有锁,持有则自动续期30s,也就是重新设置了sk_10001_stock_lock中field为客户端id的过期时间为30s

1.5 锁释放原理
public RFuture<Void> unlockAsync(long threadId) {
        // 释放锁
        RFuture<Boolean> future = unlockInnerAsync(threadId);
    
        CompletionStage<Void> f = future.handle((opStatus, e) -> {
            // 取消Watch Dog机制
            cancelExpirationRenewal(threadId);
        });

        return new CompletableFutureWrapper<>(f);
    }
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return evalWriteAsync(getRawName(), 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;",
                Arrays.asList(getRawName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
    }

由代码可知,释放锁时将sk_10001_stock_lock中field为客户端id的值依次减1,直到为0后在进行删除,删除后会向redisson_lock__channel通道中发送UNLOCK_MESSAGE消息也就是0L,通知阻塞等待的客户端

2. Redisson优缺点

优点

  • Redisson通过Watch Dog机制解决锁的续期问题
  • 与Zookeeper相比较,Redisson基于Redis性能更高
  • 通过Redisson实现分布式可重入锁,比原生的 SET mylock userId NX PX milliseconds + lua更加简洁,让我们更多关注业务逻辑
  • 在等待申请锁的实现上进行了优化,减少了无效锁申请,提升了资源利用率

缺点

在redisson3.12.5前,无法解决在master节点加锁后,master异步复制给slave时宕机,slave变为了master,其他客户端在新的master上重复加锁问题,需要使用redlock算法获取集群大多数锁时才算获取锁成功额外编码

RLock rLock = redissonClient.getLock(PRODUCT_STOCK_KEY + "_lock");
RLock rLock = redissonClient1.getLock(PRODUCT_STOCK_KEY + "_lock");
RLock rLock = redissonClient2.getLock(PRODUCT_STOCK_KEY + "_lock");
RedissonRedLock redissonRedLock = new RedissonRedLock(rLock, rLock1, rLock2);
redissonRedLock.tryLock(30, 60, TimeUnit.SECONDS);

https://github.com/redisson/redisson/blob/master/CHANGELOG.md
16-Apr-2020 - 3.12.5 released
Improvement - increased RLock reliability during failover. RedLock was deprecated

但是对于redisson3.12.5及之后就不存在该问题会等到master异步复制给slave完成后才会进行加锁

 /**
     * Returns Lock instance by name.
     * <p>
     * Implements a <b>non-fair</b> locking so doesn't guarantees an acquire order by threads.
     * <p>
     * To increase reliability during failover, all operations wait for propagation to all Redis slaves.
     *
     * @param name - name of object
     * @return Lock object
     */
    RLock getLock(String name);

欢迎关注公众号算法小生

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/8851.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录算法训练营第三十四天| LeetCode1005. K 次取反后最大化的数组和、LeetCode134. 加油站、LeetCode135. 分发糖果

一、LeetCode1005. K 次取反后最大化的数组和 1&#xff1a;题目描述&#xff08;1005. K 次取反后最大化的数组和&#xff09; 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。重复这个过…

TNF 又见 《Cell》

现有的研究表明&#xff0c;需要新的机会来增加免疫检查点封锁&#xff08;Immunecheckpoint blockade&#xff0c;ICB&#xff09;的影响。尽管干扰素&#xff08;IFN&#xff09;γ途径同时具有 ICB 抗性因子和治疗机会&#xff0c;但至今为止&#xff0c;研究人员尚未系统地…

采用新项目管理软件的四个步骤

这是采用新项目管理软件的有趣之处&#xff1a;它本身实际上是一个重大项目&#xff0c;而且您的组织越大&#xff0c;这个过程就越艰巨。 当然&#xff0c;成功的项目管理实施最终将有助于简化您的运营并最大限度地提高跨部门的效率——这有利于团队成员的士气、客户满意度…

国内外的免费AI作图工具

1.文心一格 文心一格 - AI艺术和创意辅助平台 “推荐”页面操作比较简单&#xff0c;只需要需要简单的一句话&#xff0c;等几分钟就可以直接生成&#xff1a; 主要可以用来生成不同“氛围感”十足的场景&#xff1a; 美丽的花田&#xff1a; 优点&#xff1a; 1.比较容易…

【数据结构】—— 二叉树(C)

二叉树 文章目录二叉树二叉树的概念&#xff1a;树的术语二叉树的大概样式先序创建二叉树二叉树的遍历方式先序遍历中序遍历后序遍历二叉树的概念&#xff1a; 二叉树&#xff08;Binary Tree&#xff09;是n(n>0)个结点的有限集合&#xff0c;该集合或者为空集&#xff08…

bugku-web-安慰奖

题目没给提示 点开链接 是空白页面 查看源代码 base64加密 拿去解码 备份文件 使用工具跑一下目录 &#xff08;dirsearch) 存在一个flag.php文件 但是访问没有结果 锁定index.php.bak 文件 下载下来 打开 进行代码审计 是php序列化 反序列化的内容 代码审计&…

【Python】Numpy生成坐标网格

文章目录meshgridmgrid和ogridindicesmeshgrid 在三维图的绘制过程中&#xff0c;一般需要x,y,zx,y,zx,y,z之间的对应关系&#xff0c;但对于图像而言&#xff0c;其x,yx,yx,y轴坐标是体现在像素栅格中的&#xff0c;从而图像矩阵中的像素强度&#xff0c;其实表示的是zzz轴的…

深度学习算法应用——使用LSTM对双色球进行统计与预测

前言 福彩双色球的玩法和规则是双色球投注区分为红色球号码区和蓝色球号码区&#xff0c;红色球号码从1-33&#xff0c;蓝色球号码是从1-16。投注方法是&#xff0c;从红色区选出6个不重复的号码再加上蓝色区的一个号组成一个投注组。双色球通过摇奖器确定中奖号码&#xff0c…

Zookeeper中的watch机制

客户端&#xff0c;可以通过在znode上设置watch&#xff0c;实现实时监听znode的变化Watch事件是⼀个⼀次性的触发器&#xff0c;当被设置了Watch的数据发⽣了改变的时候&#xff0c;则服务器将这个改变发送给设置了Watch的客户端⽗节点的创建&#xff0c;修改&#xff0c;删除…

观测云产品更新|新增观测云、SLS 联合解决方案;新增 3 个智能巡检配置文档;新增链路错误追踪查看器等

观测云更新 新增观测云、SLS 联合解决方案 观测云新增 SLS 存储方案&#xff0c;支持阿里云 SLS 用户能够快速使用观测云做数据查看分析。在观测云进行商业版注册/升级时&#xff0c;选择”阿里云账号结算“后&#xff0c;您可以选择SLS 存储方案&#xff0c;将数据存放在自…

SpringBoot SpringBoot 开发实用篇 1 热部署 1.3 热部署范围配置

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇1 热部署1.3 热部署范围配置1.3.1 问题引入1.3.2 热部署配置范围1.3.…

Spring Cloud(十):Spring Cloud Skywalking 以及 JavaAgent

链路追踪组件选型 Zipkin是Twitter开源的调用链分析工具&#xff0c;目前基于springcloud sleuth得到了广泛的使用&#xff0c;特点是轻量&#xff0c;使用部署简单。Pinpoint是韩国人开源的基于字节码注入的调用链分析&#xff0c;以及应用监控分析工具。特点是支持多种插件&…

CASIO程序(线路计算6.0版)

一、扩展变量设置说明 统计各种要素点的数目 各要素点数目表 名 称 平曲线交点 竖曲线变坡点 超高起始点 最多台阶数 线路导线点 数目&#xff08;个&#xff09; a b c d e 要素点数目为0时取值 -1 -5/3 0 0 0 备 注 不含起终点 不含起终点 含起…

【LeetCode 力扣】1.两数之和 Java实现 哈希表

题目链接&#xff1a;1.两数之和 1 原题描述&#xff1a; 2 解题思路 初看题目相信大家都能想到枚举的做法&#xff0c;简单来说把数组里面的所有值&#xff0c;均两两组合相加。若结果与target相等&#xff0c;则将两个数字的下标返回即可。 代码实现1&#xff1a; class …

C++入门学习5-共用体,枚举类型,宏定义

入门学习五共用体枚举类型宏定义共用体 共用体也称为联合体&#xff0c;其特点就是用一段连续的内存存储多个不同数据类型的数据&#xff0c;在写法上与结构体相似&#xff0c;但是在同一时刻&#xff0c;共用体中只有一个值是有效的&#xff0c;其大小由共用体中最大的数据类…

做PPT绝对不能错过这5个网站

免费高质量PPT模板网站&#xff0c;建议收藏&#xff01;1、菜鸟图库 https://www.sucai999.com/pptx.html?vNTYxMjky菜鸟图库里面有各种类型的PPT模板和素材。下载后模板可以直接套用&#xff0c;也可以自己添加素材进行修改。所有素材都一一进行了详细的分类&#xff0c;而且…

【前端】Flet:一款支持python及多语言开发的UI库

文章目录介绍开发生态支持语言运行体验组件API热更新开发计划 Roadmap2022 7月-8月安全手机端桌面端Controls(控件)核心功能用户指引&#xff08;User education&#xff09;2022 9月到12月手机端控件&#xff08;Controls&#xff09;编程语言支持核心功能介绍 Flet enables …

【设计模式-2】策略模式 - 避免冗余的if-else判断。数据库迁移框架、flink 类型转换框架例子对策略模式的使用

文章目录1. 介绍2. 策略模式结构3. 策略模式使用3.1. 场景一: 表迁移3.2. flink connector类型转换1. 介绍 当if else过多时&#xff0c;可以通过策略模式来进行重构。先定义一个接口&#xff0c;各else处理分支实现这个接口&#xff0c;并定义 < 条件 , 处理类 > 的映射…

NNG 通信模式

NNG 是 nanomsg 的继任版本,纯c语言开发&#xff0c;工作模式分为几种&#xff1a; 1&#xff0c;Pipeline (A One-Way Pipe) 单向通信&#xff0c;类似与生产者消费者模型的消息队列&#xff0c;消息从推方流向拉方。 #include <stdlib.h> #include <stdio.h> #…

[附源码]java毕业设计基于的图书馆管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…