Reids分布式锁详细介绍原理和实现

news2024/11/23 1:09:47

Reids 分布式锁

问题描述

1、单体单机部署的系统被演化成分布式集群系统后

2、由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效

3、单纯的Java API 并不能提供分布式锁的能力

4、为了解决这个问题就需要一种跨JVM 的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题

5、示意图(说明: 我们探讨的分布式锁是针对分布式项目/架构而言[.])

在这里插入图片描述

在这里插入图片描述

分布式锁主流实现方案

  1. 基于数据库实现分布式锁
  2. 基于缓存(Redis 等)
  3. 基于Zookeeper

每一种分布式锁解决方案都有各自的优缺点:

  1. 性能:redis 最高
  2. 可靠性:zookeeper 最高
  3. 我们讲解基于Redis 实现分布式锁

实例: Redis 实现分布式锁-基本实现

指令: setnx key value

解读:

  • setnx 可以理解是上锁/加锁指令
  • key 是锁的键
  • value 是锁的值
  • 在这个key 没有删除前, 不能执行相同key 的上锁指令.

指令: del key

解读

  • 就是删除key, 可以理解成就是释放锁

在这里插入图片描述

指令: expire key seconds

解读

  • 给锁-key, 设置过期时间
  • 目的是防止死锁

指令: ttl key

解读

  • 查看某个锁-key, 过期时间

在这里插入图片描述

指令: set key value nx ex seconds

解读

  • 设置锁的同时, 指定该锁的过期时间,防止死锁

  • 这个指令是原子性的,防止setnx key value / expire key seconds 两条指令, 中间执行被打断.

  • 过期时间到后, 会自动删除

在这里插入图片描述

实例: Redis 实现分布式锁-代码实现

需求说明/图解, 编写代码, 实现如下功能

  1. 在SpringBoot+Redis 实现分布式锁的使用
  2. 获取锁, key 为lock, 示意图
    在这里插入图片描述

第1 种情况

–如果获取到该分布式锁

–就获取key 为num 的值, 并对num+1, 再更新num 的值, 并释放锁(key 为lock)

–如果获取不到key 为num 的值, 就直接返回

第2 种情况

–如果没有获取到该分布式锁

–休眠100 毫秒, 再尝试获取

在前面的SpringBoot 整合Reids 项目上实现即可

先在Redis 初始化数据

在这里插入图片描述

修改RedisTestController

\src\main\java\com\redis\controller\RedisTestController.java , 增加API 接口

@GetMapping("testLock")
    public void testLock() {
        //1 获取锁,setnx
        Boolean lock =
                redisTemplate.opsForValue().setIfAbsent("lock", "ok");
            //2 获取锁成功、查询num 的值
        if (lock) {
            Object value = redisTemplate.opsForValue().get("num");
            //2.1 判断num 为空return
            if (value == null || !StringUtils.hasText(value.toString())) {
            return;
            }
            //2.2 有值就转成成int
            int num = Integer.parseInt(value.toString());
            //2.3 把redis 的num 加1
            redisTemplate.opsForValue().set("num", ++num);
            //2.4 释放锁,del
            redisTemplate.delete("lock");
        } else {
            //3 获取锁失败、每隔0.1 秒再获取
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

启动SpringBoot 项目

在这里插入图片描述

保证Linux 可以访问到SpringBoot 项目

在这里插入图片描述

在这里插入图片描述

使用ab 工具完成测试

ab -n 1000 -c 100 http://192.168.198.1:8080/redisTest/testLock

在这里插入图片描述

在这里插入图片描述

实例: 优化-设置锁的过期时间, 防止死锁

问题分析

假如在执行关闭锁之前就发生异常然后没有执行释放锁 然后我们又没有设置过期时间 所以就会死锁

  1. 在前面代码上修改,设置锁的过期时间
  2. 防止死锁

修改这一句就好了

注意这在实际开发中锁过期的时间是需要经过严格的考虑的不然设小了没有起效果 设置大了效率低

Boolean lock =
redisTemplate.opsForValue().setIfAbsent("lock", "ok", 3, TimeUnit.SECONDS);

完成测试

ab -n 1000 -c 100 http://192.168.198.1:8080/redisTest/testLock

在这里插入图片描述

在这里插入图片描述

注意因为我们前面测试过一次了所以再测试就是2000次了

实例: 优化-UUID 防误删锁

问题分析, 如图

在这里插入图片描述

思路分析

  1. 在获取锁的时候, 给锁设置的值是唯一的uuid
  2. 在释放锁时,判断释放的锁是不是同一把锁.
  3. 造成这个问题的本质原因, 是因为删除操作缺乏原子性

修改RedisTestController

 @GetMapping("testLock")
    public void testLock() {
        //1 获取锁,setnx
        //得到一个uuid 值,作为锁的值
        String uuid = UUID.randomUUID().toString();
        Boolean lock =
                redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        //2 获取锁成功、查询num 的值
        if (lock) {
            Object value = redisTemplate.opsForValue().get("num");
            //2.1 判断num 为空return
            if (StringUtils.isEmpty(value)) {
                return;
            }
            //2.2 有值就转成成int
            int num = Integer.parseInt(value + "");
            //2.3 把redis 的num 加1
            redisTemplate.opsForValue().set("num", ++num);
            //2.4 释放锁,del
            //为了防止误删锁, 进行判断
            //判断当前这个锁是不是前面获取到的锁, 相同才进行删除/释放
            if (uuid.equals((String) redisTemplate.opsForValue().get("lock"))) {
                redisTemplate.delete("lock");
            }
        //redisTemplate.delete("lock");
        } else {
        //3 获取锁失败、每隔0.1 秒再获取
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

完成测试

ab -n 1000 -c 100 http://192.168.198.1:8080/redisTest/testLock

在这里插入图片描述

在这里插入图片描述

实例: 优化-LUA 脚本保证删除原子性

当前代码问题分析, 如图

在这里插入图片描述

思路分析

  1. 删除操作缺乏原子性
  2. 使用Lua 脚本保证删除原子性

修改RedisTestController

@RestController
@RequestMapping("/redisTest")
public class RedisTestController {

    //装配RedisTemplate
    @Resource
    private RedisTemplate redisTemplate;

    //编写方法,使用Redis分布式锁,完成对 key为num的+1操作
    @GetMapping("/lock")
    public void lock() {

        //得到一个uuid值,作为锁的值
        String uuid = UUID.randomUUID().toString();

        //1. 获取锁/设置锁 key->lock : setnx
        Boolean lock =
                redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        if (lock) {//true, 说明获取锁/设置锁成功
            //这个key为num的数据,事先要在Redis初始化
            Object value = redisTemplate.opsForValue().get("num");
            //1.判断返回的value是否有值
            if (value == null || !StringUtils.hasText(value.toString())) {
                return;
            }
            //2.有值,就将其转成int
            int num = Integer.parseInt(value.toString());
            //3.将num+1,再重新设置回去
            redisTemplate.opsForValue().set("num", ++num);
            //释放锁-lock


            //为了防止误删除其它用户的锁,先判断当前的锁是不是前面获取到的锁,如果相同,再释放

            //=====使用lua脚本, 控制删除原子性========
            // 定义lua 脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            // 使用redis执行lua执行
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(script);
            // 设置一下返回值类型 为Long
            // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
            // 那么返回字符串与0 会有发生错误。
            redisScript.setResultType(Long.class);
            // 第一个是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值
            // 解读 Arrays.asList("lock") 会传递给 script 的 KEYS[1] , uuid 会传递给ARGV[1] , 其它的应该很容易理解
            redisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);


            //if (uuid.equals((String) redisTemplate.opsForValue().get("lock"))) {
            //    //...
            //    redisTemplate.delete("lock");
            //}

            //redisTemplate.delete("lock");

        } else { //获取锁失败,休眠100毫秒,再重新获取锁/设置锁

            try {
                Thread.sleep(100);
                lock();//重新执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Lua 脚本详解

在这里插入图片描述

完成测试

ab -n 1000 -c 100 http://192.168.198.1:8080/redisTest/testLock

在这里插入图片描述
在这里插入图片描述

注意事项和细节

1、定义锁的key, key 可以根据业务, 分别设置,比如操作某商品, key 应该是为每个sku 定义的,也就是每个sku 有一把锁

2、为了确保分布式锁可用,要确保锁的实现同时满足以下四个条件:

  • 互斥性。在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 加锁和解锁必须是同一个客户端,A 客户端不能把B 客户端加的锁给解了
  • 加锁和解锁必须具有原子性

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

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

相关文章

abd shell后,getevent退出方法

abd shell后&#xff0c;getevent退出方法 输入 exit 然后回车退出

一种很新的交互式智能标注技术

随着人工智能应用的大规模落地&#xff0c;数据标注市场在高速增长的同时&#xff0c;也面临着标注成本的挑战。据IDC报告显示&#xff1a;数据标注在AI应用开发过程中所耗费的时间占到了25%&#xff0c;部分医学类应用一条数据的标注成本甚至高达20元。数据精度的高要求、强人…

RocketMQ 环境搭建

环境&#xff1a;linux&#xff08;centos&#xff09; 或 windos&#xff1b; jdk 1.8 场景&#xff1a;rocket入门学习 时间&#xff1a;2023-04-20 吐槽&#xff1a;可能是本人学习能力不足&#xff0c;想使用docker搭建rocketmq 一直失败&#xff0c;可能是我想使用的比较新…

正排倒排,并不是 MySQL 的排序的全部!

引言 一个悠闲的上午&#xff0c;小航送了我&#xff0c;一袋坚果&#xff0c;他看我吃的正香&#xff0c;慢慢问道&#xff1a;”温哥&#xff0c;mysql的排序&#xff0c;有什么要注意的吗&#xff0c;不就是正排倒排吗&#xff1f;” 我一听他问我的问题&#xff0c;顿感坚…

软件测试简历如何包装?

首先明确的包装简历不等于欺骗&#xff0c;只是把你的最好一面展示出来&#xff0c;给别人一个好的映像&#xff1b;&#xff08;就相当于相亲&#xff0c;哈哈&#xff09; 无论如何包装简历&#xff0c;注意简历上的东西一定要会、一定要会、一定要会&#xff08;面试官一般…

Java框架-Spring

文章目录 1、你了解Spring IOC吗&#xff1f;2、SpringIOC的应用&#xff1f;3、SpringIOC的getBean方法的解析&#xff1f;4、面试题5、你了解Spring AOP吗&#xff1f;6、事务ACID特性7、事务传播 1、你了解Spring IOC吗&#xff1f; IoC&#xff08;Inversion of control&a…

C++编程启蒙-2——你适合学习编程吗?

英语差&#xff0c;数学孬&#xff0c;照样可以学好编程。但&#xff0c;如果你逻辑思维差&#xff0c;动力能力弱&#xff0c;那么学习编程真的会难上加难。本课用来帮助读者实现对逻辑思维与动手能力的自我判断&#xff0c;并给出了实际测试方案。 英语差&#xff0c;数学孬&…

15个常见的AI绘画网站推荐

无论你是专业的艺术家还是对人工智能绘画感兴趣的普通人&#xff0c;AI绘画网站都可以为你提供新的创作灵感和艺术体验&#xff0c;给艺术界带来更多的创新和可能性。以下是15个常见的AI绘画网站的介绍。 即时 AI 灵感 「即时 AI 灵感」是通过文字描述等方式生成精致图像的AI…

QGIS实现shape、geojson数据的矢量切片教程

能够实现矢量切片的办法有很多&#xff0c;可以使用geoserver&#xff0c;可以使用qgis&#xff0c;当然也可以自己写代码实现。这篇文章我们来介绍一下如何使用qgis完成shape数据的矢量切片。 首先我们还是要准备一份矢量数据。矢量数据的格式是shape文件或者是geojson文件都…

IDEA下载安装与使用

IDEA下载、安装与概述、使用 IDEA全称InteliJ IDEA&#xff0c;是用于Java语言开发的集成环境&#xff0c;它是业界公认的目前用于Java程序开发最好的工具 集成环境&#xff1a;把代码编写、编译、执行、调试等多种功能综合到一起的开发工具 1 IDEA的下载 官网链接&#xf…

第一章 基础算法(二)——高精度,前缀和与差分

文章目录 高精度运算高精度加法高精度减法高精度乘法高精度除法 前缀和二维前缀和 差分二维差分 高精度练习题791. 高精度加法792. 高精度减法793. 高精度乘法794. 高精度除法 前缀和练习题795. 前缀和796. 子矩阵的和 差分练习题797. 差分798. 差分矩阵 高精度运算 两个大数做…

Day37

思维导图 练习 1> 编写一个名为myfirstshell.sh的脚本&#xff0c;它包括以下内容。 a、包含一段注释&#xff0c;列出您的姓名、脚本的名称和编写这个脚本的目的 b、和当前用户说“hello 用户名” c、显示您的机器名 hostname d、显示上一级目录中的所有文件的列表 e、显示…

Git 多账号多仓库配置 SSH

前言 在我们使用 Git 中&#xff0c;有时候会遇到多账号多仓库的情况&#xff0c;比如公司的 GitLab 和 GitHub&#xff0c;以及自己的 GitHub&#xff0c;这时候我们就需要配置多个 SSH 密钥来区分不同的账号和仓库 生成 SSH 密钥 根据你注册仓库的邮箱生成 SSH 密钥&#…

Kubeadm方式搭建K8s集群 1.27.0版本

目录 一、集群规划 二、系统初始化准备(所有节点同步操作) 三、安装并配置cri-docker插件 四、安装kubeadm&#xff08;所有节点同步操作&#xff09; 五、初始化集群 六、Node节点添加到集群 七、安装网络组件Calico 八、测试codedns解析可用性 一、集群规划 环境规划…

Qt 定时器

定时器事件---timerEvent 定时器启动 startTimer(); 注意&#xff1a;定时器参数要是全局或者静态变量。 定时器结束 没有结束函数 定时器标志---timerId int timerIdstartTimer(); startTimer()返回定时器标志 需设置为类的成员--类内使用 定时器使用 通过定时器事件…

设计模式介绍

设计模式的分类 总体来说设计模式分为三大类&#xff1a; 创建型模式&#xff0c;共五种&#xff1a;工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式&#xff0c;共七种&#xff1a;适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式…

JVM体系结构模块

1、类装载器ClassLoader &#xff08;1&#xff09;负责加载class文件&#xff0c;class文件在文件开头有特定的文件标示&#xff0c;并且ClassLoader只负责class文件的加载&#xff0c;至于它是否可以运行&#xff0c;则由Execution Engine决定 &#xff08;2&#xff09;加…

麻了。。。

同样的初始条件&#xff0c;一个每隔0.05s保存一步数据&#xff0c;一个每隔1.13s保存一步数据。 上面横轴代表时间&#xff0c;纵轴代表Nu。 可以看出其实是不太一样的&#xff0c;一个取平均是24.72&#xff0c;一个是25.34&#xff0c;差距其实有的。 我目前跑算例发现长时间…

【C++】类和对象超全超详细总结(万字详解)

&#x1f680; 作者简介&#xff1a;一名在后端领域学习&#xff0c;并渴望能够学有所成的追梦人。 &#x1f681; 个人主页&#xff1a;不 良 &#x1f525; 系列专栏&#xff1a;&#x1f6f8;C &#x1f6f9;Linux &#x1f4d5; 学习格言&#xff1a;博观而约取&#xff0…

C#视觉检测-模板匹配

前几天一个学员在学习C#与视觉交互时,也不知道视觉可以用来做什么 。下面我们就详细讲讲C# 和视觉交互的相关知识。 C#和Halcon的视觉交互在工业生产和智能制造领域中得到了广泛应用。其中&#xff0c;模板匹配是一种简单但有效的图像处理技术&#xff0c;可以用于检测和定位物…