Redis分布式锁学习总结

⭐️ 前言

想必大家都有过并发编程的经验,在一个单体应用中,可以通过java提供的各种锁机制来控制多线程对于单体应用中同一资源的并发访问;那么在分布式场景下,想要控制多个应用对于同一外部资源的并发访问,就要用到分布式锁。分布式锁不但要保证单个应用程序内部不会产生并发问题,同时也要保证多个应用程序之间不能产生并发问题。分布式锁有很多实现方式,比如使用redis、zookeeper或关系型数据库的唯一索引,也有现成的分布式锁架构,比如redisson、curator等。本文利用spring-data-redis手动实现一个简易的redis分布式锁,剖析redis分布式锁的原理。
在这里插入图片描述

⭐️ redis分布式锁实现原理浅析

实现redis分布式锁最简单的想法就是各个应用利用setnx命令向redis中争抢设置key的机会,但我们还应该考虑得更加周全。

死锁

如果成功加锁的应用程序在未释放锁之前就异常终止了,那么这个锁永远无法释放,其他应用程序则永远也无法获取到锁,为了解决这个问题,需要给代表锁的redis的key加上过期时间。

原子性

很多时候我们需要保证多个操作具有原子性,
例如,加锁和设置过期时间
若它们无法保证原子性,则应用程序在刚刚成功加锁后就异常终止了,则仍然会出现上面的死锁问题。

原子性可以通过让redis执行Lua脚本来保证,eval命令可以原子性的执行Lua脚本(Lua的多个步骤会被原子性的执行),在redis内置的Lua脚本中有一个redis对象,可以通过redis.call()方法执行各种redis命令。

防误删

根据上面的讨论,我们需要给redis锁加上过期时间,当业务执行完毕之前锁就过期了,这种情况下,其他线程就会成功加锁,那么之前的程序运行到解锁逻辑时,就会造成对后面线程获得锁的误删。
在这里插入图片描述
误删可以通过给每一个线程设置一个id,我们可以叫它 线程标识码

可重入性

另外,在应用程序中免不了方法的彼此调用,若锁无法重入,则业务根本无法执行,比如A方法需要加锁,它在执行过程中会调用B方法,B方法也需要加锁,若锁不具有可重入性,则程序根本无法运行。

可重入性可以通过hash数据结构来实现
key: 代表锁的key
field:线程的唯一标识id,即上文说的 线程标识码
value:重入次数

自动续期

若应用程序执行需要的时间大于锁的过期时间,则锁过期后,应用程序便不再受锁保护,这样就会导致并发问题。所以分布式锁还要具有自动续期的功能,即只要应用程序业务没有执行完毕,则锁需要不断的自动延长过期时间。
自动续期可以通过Timer定时任务配合Lua脚本来实现。

本文参考了上硅谷课程《【尚硅谷】分布式锁全家桶丨一套搞定Redis/Zookeeper/MySQL实现分布式锁》,B站上就有,更详细的内容读者可以去看这门课程。

这里贴出关键代码,完整代码我已经上传到了gitee。欢迎围观啊!!

⭐️ 加锁主要代码

/**
     * 加锁
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time != -1){
            this.expire = unit.toSeconds(time);
        }
        // redis加锁的lua脚本
        String lockStr="if redis.call('exists', KEYS[1])==0 or redis.call('hexists', KEYS[1], ARGV[1])==1 " +
                "then " +
                "redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                "redis.call('expire', KEYS[1], ARGV[2]) " +
                "return 1 " +
                "end " +
                "return 0";
        // 加锁
        while (!this.redisTemplate.execute(new DefaultRedisScript<>(lockStr, Boolean.class), Arrays.asList(this.lockName), this.uuid, String.valueOf(this.expire))){
            Thread.sleep(50);
        }
        // 自动续期
        this.autoExpire();
        return true;
    }

⭐️ 解锁主要代码

/**
     * 解锁
     */
    @Override
    public void unlock() {
        // 解锁lua脚本
        String unlockStr = "if redis.call('hexists', KEYS[1], ARGV[1])==0 " +
                "then " +
                "return nil " +
                "end " +
                "if redis.call('hincrby', KEYS[1], ARGV[1], -1)==0 " +
                "then " +
                "return redis.call('del', KEYS[1]) " +
                "end " +
                "return 0";
        // 解锁
        Long del = this.redisTemplate.execute(new DefaultRedisScript<>(unlockStr, Long.class), Arrays.asList(this.lockName), this.uuid);
        if (del == null){
            throw new IllegalMonitorStateException("lock wrong");
        }
        if (del == 1L){
            System.out.println("lock deleted");
        }
    }

⭐️ 自动续期主要代码

/**
     * 自动续期
     */
    private void autoExpire(){
        String expireStr="if redis.call('hexists', KEYS[1], ARGV[1])==1 " +
                         "then return redis.call('expire', KEYS[1], ARGV[2]) " +
                         "end " +
                         "return 0";

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                if (redisTemplate.execute(new DefaultRedisScript<>(expireStr, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))){
                    autoExpire();
                }
            }
        }, this.expire * 1000 /3);
    }

⭐️ 运行架构

运行架构比较简单,可以启动两个应用实例,利用nginx做负载均衡。
在这里插入图片描述

⭐️ 压力测试

压力测试可以使用jmeter,其设置如下图所示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
笔者水平有限,若有不对的地方欢迎评论指正!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用YOLOv8训练自己的数据集

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 拉取项目 git clone https://github.com/ultralytics/ultralytics安装依赖 cd ultralytics pip install -r requirement.txt pip instal…

JAVA全栈开发 集合详解(day14+day15汇总)

一、数组 数组是一个容器&#xff0c;可以存入相同类型的多个数据元素。 数组局限性&#xff1a; ​ 长度固定&#xff1a;&#xff08;添加–扩容&#xff0c; 删除-缩容&#xff09; ​ 类型是一致的 对象数组 &#xff1a; int[] arr new int[5]; … Student[] arr …

一种LED驱动专用控制电路方案

一、基本的概述 TM1651 是一种带键盘扫描接口的LED&#xff08;发光二极管显示器&#xff09;驱动控制专用电路&#xff0c;内部集成有MCU 数字接口、数据锁存器、LED 高压驱动、键盘扫描等电路。本产品性能优良&#xff0c;质量可靠。采用SOP16/DIP16的封装形式。 二、特性说…

微软 Power Platform 零基础 Power Pages 网页搭建教程学习实践(一)

微软 Power Platform 零基础 Power Pages 网页搭建教程学习实践 Power Pages 网页搭建 微软 Power Platform 零基础 Power Pages 网页搭建教程学习实践1、Power Pages 介绍2、开始创建一个站点3、选择一个合适的模板4、编辑我们的模板5、面向专业开发人员的高级开发功能6、预览…

PyQt基础_012_对话框类控件QInputDialog

基本操作 import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import *class InputdialogDemo(QWidget):def __init__(self, parentNone):super(InputdialogDemo, self).__init__(parent)layout QFormLayout()self.btn1 QPushButton(&qu…

[PM3教程]华为手机,小米手环,手表模拟写入加密门禁卡必看教程

IC卡在我们身边已随处可见&#xff0c;被广泛应用于各种领域。 大多数人每天都要和各种各样的卡片打交道&#xff0c;上班有考勤卡&#xff0c;吃饭有饭卡&#xff0c;健身有会员卡&#xff0c;停车有停车卡&#xff0c;连回个家都得先把门禁卡翻出来。各种各样的卡&#xff0c…

借鉴halcon中inspect_3d_surface_intersections.hdev示例

简单看下halcon的实现过程 二、halcon思路 1、读入图片 2、生成点云模型&#xff0c;将点云三角化 3、生成平面 4、求这个模型与平面的交线&#xff0c;生成一个轮廓 用pcl和vtk实现的效果 主要参考以下博主的文章内容来实现的 鞋3D点胶 halcon切平面算法_pose_invert-C…

centOS使用docker部署ElasticSearch和Kibana

一、docker部署ElasticSearch 1、创建网桥 docker network create xybnet 2、下载镜像 docker pull elasticsearch:8.2.0 3、先运行容器 docker run -d \ --name es \ --net xybnet \ -p 9200:9200 \ -p 9300:9300 \ -p 5601:5601 \ -e "discovery.typesing…

TCP/IP_整理起因

先分享一个初级的问题&#xff1b;有个客户现场&#xff0c;终端设备使用客户网络更新很慢&#xff0c;使用手机热点更新速度符合预期&#xff1b;网络部署情况如下&#xff1a; 前期花费了很大的精力进行问题排查对比&#xff0c;怀疑是客户网络问题&#xff08;其他的客户现…

进程间通信2

3. system V-IPC 3.1 知识点 ipcs -a查看所有的ipc对象 在系统中他们都使用一种叫做 key 的键值来唯一标识&#xff0c;而且他们都是“持续性”资源——即他 们被创建之后&#xff0c;不会因为进程的退出而消失&#xff0c;而会持续地存在&#xff0c;除非调用特殊的函数或者…

春秋云镜ED01-CMS v20180505 存在任意文件上传漏洞

靶场介绍 春秋云镜ED01-CMS v20180505 存在任意文件上传漏洞 漏洞分析&#xff1a; 文件类型未校验可以任意上传执行文件&#xff0c;获取服务器权限 登录注册界面 Hi-Lo-Yohttp://eci-2ze2qm1cbaon2lylin0q.cloudeci1.ichunqiu.com/registration.php 注册了几个发现注册不…

Echarts大屏可视化_03 定制柱状图

柱状图模块引入 1.找到合适的图表 在echarts中寻找与目标样式相近的图表 Examples - Apache ECharts 2. 引入柱状图 使用立即执行函数构建&#xff0c;防止变量全局污染 实例化对象 将官网中提供的option复制到代码中&#xff0c;并且构建图表 // 柱状图模块1 (function () {/…

【算法】算法题-20231128

这里写目录标题 一、55. 跳跃游戏二、274. H 指数三、125. 验证回文串 一、55. 跳跃游戏 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&am…

sqli-labs(9)

45. 不会显示报错信息通过or 1验证 在密码处输入)or(1 登录成功 )union select 1,2,3 # )union select 1,database(),3 # )union select 1,(select group_concat(table_name) from information_schema.tables where table_schemasecurity),3 # )union select 1,(select gro…

使用libssh2建立安全的SSH连接:C++开发者的综合指南

使用libssh2建立安全的SSH连接&#xff1a;C开发者的综合指南 一、介绍二、准备工作三、建立SSH连接3.1、初始化libssh2库3.2、连接到远程主机3.4、完整示例 四、文件传输4.1、上传文件到远程主机4.2、下载文件到本地主机 五、总结 一、介绍 SSH和安全连接的重要性是不可忽视的…

【CAD二次开发】标注箭头,获取修改标注箭头图块

常见的的标注箭头有以下种类 public static List<string> ArrowBlock = new List<string>(){" ","_CLOSEDBLANK&

dubbo框架技术文档-《spring-boot整合dubbo框架搭建+配置文件》框架的本地基础搭建

阿丹&#xff1a; 目前流行的微服务更多的就是dubbo和springcould微服务。之前阿丹没有出过dubbo相关的文章&#xff0c;因为之前接触springcould的微服务概念比较多一点&#xff0c;但是相对于springcould来说&#xff0c;springcould服务之间的调用是大多是使用了nacos&#…

中学老师求职简历(精选9篇)

以下简历内容以中学老师招聘需求为背景&#xff0c;我们整理并修改了9篇全面、专业且具有参考价值的简历案例&#xff0c;大家可以灵活借鉴&#xff0c;希望能帮助大家在众多候选人中脱颖而出。 中学老师简历下载&#xff08;可在下制作下载&#xff09;&#xff1a;百度幻主简…

保障美味不失传,上海迅软DSE为餐饮业提供一键式数据高效备份服务!

如今&#xff0c;随着经济技术的飞速发展&#xff0c;餐饮行业对各项业务与财务数据的容灾能力要求越来越高。信息数据不仅要做好安全备份&#xff0c;而且出现故障后&#xff0c;还要能及时、准确、安全、完整的进行恢复。 餐饮行业数据安全存在的隐患 1.餐饮行业各项业务与财…

剪辑必备AI去水印神器,手把手教你轻松消除图片水印

当我们的剪辑制作过程中&#xff0c;前期需要准备图片或视频素材&#xff0c;水印往往成为了我们首要解决的难题。 幸运的是&#xff0c;今天我为大家介绍一款在线AI去水印神器--水印云。 水印云是一个的在线去除图片水印工具。仅需三步&#xff0c;即可使用强大的 AI 技术从图…