redis分布式锁+redisson框架

news2024/11/24 21:05:31

目录

🧂1.锁的类型

🌭2.基于redis实现分布式

 🥓3. 基于redisson实现分布式锁


1.锁的类型

  • 1.本地锁:synchronize、lock等,锁在当前进程内,集群部署下依旧存在问题
  • 2.分布式锁:redis、zookeeper等实现,虽然还是锁,但是多个进程共用的锁标记,可以用Redis、Zookeeper, Mysql等都可以

2.基于redis实现分布式锁

1.加锁 setnx key value

  • setnx 的含义就是 SET if Not Exists,有两个参数 setnx(key, value),该方法是原子性操作
  • 如果 key 不存在,则设置当前 key 成功,返回 1;
  • 如果当前 key 已经存在,则设置当前 key 失败,返回 0

2.解锁 del(key)

得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入,调用 del(key) 

3.配置锁超时 expire(key,30)

客户端崩溃或者网络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放 

4.代码 

正常的加锁逻辑,但存在问题

  public void coupon(){
        String key="coupon_id";
        if (setnx(key,1)==1){
            expire(key,30, TimeUnit.MILLISECONDS)
            try{
                //业务
            }finally {
                del(key);
            }
        }else {
            //睡眠,然后自旋调用
            coupon();
        }
    }

5.存在的问题 

  • 1.问题一
    • 多个命令之间不是原子性操作,如setnx和expire之间,如果setnx成功,则这个资源就是死锁但是expire失败,且宕机了,则这个资源就是死锁。
  • 2.解决
    • 使用原子命令:同时设置和配置过期时间 setnx / setex
    • redisTemplate.opsForValue().setIfAbsent(key, v: "lock", l: 30,TimeUnit.SECoNDS);
  • 1.问题二 :
    • 业务超时,存在其他线程勿删,key 30秒过期,假如线程A执行很慢超过30秒,则key就被释放了,其他线程B就得到了锁,这个时候线程A执行完成,而B还没执行完成,结果就是线程A删除了线程B加的锁
  • 2.解决
    • 可以在 del 释放锁之前做一个判断,验证当前的锁是不是自己加的锁,那 value 应该是存当前线程的标识或者uuid
  • 3.但是,删除锁时也不是原子性操作。
    • 当线程A获取到正常值时,返回带代码中判断期间锁过期了,线程B刚好重新设置了新值,线程A那边有判断value是自己的标识,然后调用del方法,结果就是删除了新设置的线程B的值
    • 核心还是判断和删除命令 不是原子性操作导致

6.使用lua脚本

  • 问题:加锁使用setnx setex可以保证原子性,那解锁使用判断和删除怎么保证原子性 ????
  • 多个命令的原子性:采用lua脚本+redis,由于【判断和删除】是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分布式锁,正常严谨的逻辑 

redisTemplate.execute返回值不必须是 long 类型,但你必须确保 Redis 脚本返回的数据与你在 DefaultRedisScript 中指定的类型相匹配。

public class Main {
    @Autowired
    StringRedisTemplate redisTemplate;

    /**
     * 分布式锁-redis
     */
    @Test
    public void test() {
        //根据业务,动态获取key
        String key = "coupon_id";
        //随机生成uuid,作为value值
        String uuid = CommonUtil.generateUUID();
        //设置key的同时,设置过期时间;获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(key, uuid, 30, TimeUnit.MILLISECONDS);
        //判断是否加锁成功
        if (lock) {
            //枷锁成功
            try {
                //执行业务
            } finally {
                //释放锁,使用lua脚本保持原子性
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                //执行lua脚本
                Integer result = redisTemplate.execute(new DefaultRedisScript<>(script, Integer.class), Arrays.asList(key), uuid);
            }
        } else {
            //睡眠一定时间
            //加锁失败,自旋
            test();
        }
    }
}
  • 锁的过期时间,如何实现锁的自动过期 或者 避免业务执行时间过长,锁过期了?
  • 答:一般把锁的过期时间设置久一点,比如10分钟时间 

 3. 基于redisson实现分布式锁

1.添加依赖

            <!--分布式锁-->
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.12.0</version>
            </dependency>

2.添加redissonClient

如果报错的话,查看java版本是否正确

@Configuration
@Data
public class RedissonConfig {

    //从配置文件获取redis的相关信息
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private String redisPort;

    /**
     * 使用redisson作为分布式锁
     * @return
     */
    @Bean
    public RedissonClient redissonClient() {
        //1.创建配置对象
        Config config = new Config();
        //2.连接redis
        config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort);
        //3,创建redissonClient对象
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

3.实现分布式锁

myLock.lock()如果不指定过期时间,则默认30s后过期

  @Autowired
    private RedissonClient redissonClient;

    @GetMapping("/lock")
    public JsonData test() {
        //1.获取锁
        RLock myLock = redissonClient.getLock("my_lock");
        //2.手动加锁
        myLock.lock();
        try {
            //3.业务实现
            System.out.println("加锁成功~" + Thread.currentThread().getName());
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //4.手动解锁
            myLock.unlock();
            System.out.println("解锁成功~" + Thread.currentThread().getName());
        }
        return JsonData.buildSuccess();
    }

4.看门狗机制 

Redis锁的过期时间小于业务的执行时间该如何续期?

  • 为了避免这种情况的发生, Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。
  • Redisson中客户端一旦加锁成功,就会启动一个watch dog看门狗。watch dog是一个后台线程,会每隔10秒检查一下,如果客户端还持有锁key,那么就会不断的延长锁key的生存时间。
  • 1.指定加锁时间 

myLock.lock(10, TimeUnit.SECONDS) 

  • 指定加锁时间后,默认10秒后过期,并且没有看门狗机制
  • 2.不指定加锁时间

 myLock.lock(10, TimeUnit.SECONDS) 

  • 默认30秒后过期,存在看门狗机制
  • 只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔(【LockWatchingTimeOut看门狗默认时间】/3)这么长时间自动续期;

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

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

相关文章

OLAP介绍

OLAP OLAP介绍 Rollup OLAP&#xff08;在线分析处理&#xff09;的上下文中&#xff0c;"Rollup"是一个重要的概念&#xff0c;它指的是在多维数据集中自动地聚合数据到更高的层次或维度的过程。这种操作通常用于快速计算和展示汇总数据&#xff0c;以便于用户进…

包和final.Java

1&#xff0c;包 包就是文件夹。用来管理不同功能的Java类&#xff0c;方便后期代码的维护。 &#xff08;1&#xff09;包名的规则是什么&#xff1f; 公司域名反写报的作用&#xff0c;需要全部英文小写&#xff0c;见名知意。com.itheima.domain &#xff08;2&#xff…

15.队列集

1.简介 在使用队列进行任务之间的“沟通交流”时&#xff0c;一个队列只允许任务间传递的消息为同一种数据类型&#xff0c;如果需要在任务间传递不同数据类型的消息时&#xff0c;那么就可以使用队列集。FreeRTOS提供的队列集功能可以对多个队列进行“监听”&#xff0c;只要…

Redis高级-分布式缓存

分布式缓存 – 基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题&#xff1a; 0.目标 1.Redis持久化 Redis有两种持久化方案&#xff1a; RDB持久化AOF持久化 1.1.RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;…

QT drawPixmap和drawImage处理图片模糊问题

drawPixmap和drawImage显示图片时&#xff0c;如果图片存在缩放时&#xff0c;会出现模糊现象&#xff0c;例如将一个100x100 的图片显示到30x30的区域&#xff0c;这个时候就会出现模糊。如下&#xff1a; 实际图片&#xff1a; 这个问题就是大图显示成小图造成的像素失真。 当…

FPGA(Verilog)实现按键消抖

实现按键消抖功能&#xff1a; 1.滤除按键按下时的噪声和松开时的噪声信号。 2.获取已消抖的按键按下的标志信号。 3.实现已消抖的按键的连续功能。 Verilog实现 模块端口 key_filter(input wire clk ,input wire rst_n ,input wire key_in , //按下按键时为0output …

[NKCTF2024]-PWN:leak解析(中国剩余定理泄露libc地址,汇编覆盖返回地址)

查看保护 查看ida 先放exp 完整exp&#xff1a; from pwn import* from sympy.ntheory.modular import crt context(log_leveldebug,archamd64)while True:pprocess(./leak)ps[101,103,107,109,113,127]p.sendafter(bsecret\n,bytes(ps))cs[0]*6for i in range(6):cs[i]u32(p…

6.模板初阶(函数模板、类模板、类模板声明与定义分离)

1. 泛型编程 如何实现一个通用的交换函数呢&#xff1f; 使用函数重载虽然可以实现&#xff0c;但是有一下几个不好的地方&#xff1a; 重载的函数仅仅是类型不同&#xff0c;代码复用率比较低&#xff0c;只要有新类型出现时&#xff0c;就需要用户自己增加对应的函数代码的…

线性、逻辑回归算法学习

1、什么是一元线性回归 线性&#xff1a;两个变量之间的关系是一次函数&#xff0c;也是数据与数据之间的关系。 回归&#xff1a;人们在测试事物的时候因为客观条件所限&#xff0c;求的都是测试值&#xff0c;而不是真实值&#xff0c;为了无限接近真实值&#xff0c;无限次的…

HarmonyOS开发实例:【状态管理】

状态管理 ArkUI开发框架提供了多维度的状态管理机制&#xff0c;和UI相关联的数据&#xff0c;不仅可以在组件内使用&#xff0c;还可以在不同组件层级间传递&#xff0c;比如父子组件之间&#xff0c;爷孙组件之间等&#xff0c;也可以是全局范围内的传递&#xff0c;还可以是…

【考研数学】1800还是660还是880?

关于这几本习题册如何选择&#xff0c;肯定是根据他们的不同特点以及我们的需求结合选择&#xff0c;给大家的建议如下&#xff1a; 1800适合初期&#xff0c;可以帮助你熟悉数学公式和基础定义&#xff0c;迅速上手用。刚开始觉得难很正常&#xff0c;存在一个上手的过程&…

VRRP虚拟路由实验(思科)

一&#xff0c;技术简介 VRRP&#xff08;Virtual Router Redundancy Protocol&#xff09;是一种网络协议&#xff0c;用于实现路由器冗余&#xff0c;提高网络可靠性和容错能力。VRRP允许多台路由器共享一个虚拟IP地址&#xff0c;其中一台路由器被选为Master&#xff0c;负…

【Erlang】【RabbitMQ】Linux(CentOS7)安装Erlang和RabbitMQ

一、系统环境 查版本对应&#xff0c;CentOS-7&#xff0c;选择Erlang 23.3.4&#xff0c;RabbitMQ 3.9.16 二、操作步骤 安装 Erlang repository curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash安装 Erlang package s…

扫描IP开放端口该脚本用于对特定目标主机进行常见端口扫描(加载端口字典)或者指定端口扫描,判断目标主机开

扫描IP开放端口该脚本用于对特定目标主机进行常见端口扫描(加载端口字典)或者指定端口扫描,判断目标主机开 #/bin/bash #该脚本用于对特定目标主机进行常见端口扫描(加载端口字典)或者指定端口扫描,判断目标主机开放来哪些端口 #用telnet方式 IP$1 #IP119.254.3.28 #获得IP的前…

【STL】顺序容器与容器适配器

文章目录 1顺序容器概述1.1array1.2forward_list1.3deque 2.如何确定使用哪种顺序容器呢&#xff1f;3.容器适配器的概念4.如何定义适配器呢&#xff1f; 1顺序容器概述 给出以下顺序容器表&#xff1a; 顺序容器类型作用vector可变大小的数组&#xff0c;支持快速访问&#…

UML学习

UML(Unified Modeling Language)&#xff1a;统一建模语言&#xff0c;提供了一套符号和规则来帮助分析师和设计师表达系统的架构、行为和交互 类图&#xff1a;描绘类、接口之间的关系(继承、实现、关联、依赖等)以及类的内部结构(属性和方法)&#xff0c;直观展现系统的静态…

2024年3月电子学会青少年软件编程 中小学生Python编程等级考试一级真题解析(判断题)

2024年3月Python编程等级考试一级真题解析 判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 26、turtle 画布的坐标系原点是在画布的左上角 答案&#xff1a;错 考点分析&#xff1a;考查turtle相关知识&#xff0c;turtle画布坐标系是在画布的…

c# wpf LiveCharts 饼图 简单试验

1.概要 c# wpf LiveCharts 饼图 简单试验 2.代码 <Window x:Class"WpfApp3.Window5"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schem…

openharmony launcher 调研笔记(03)UI 数据装配

最近在看launcher&#xff0c;把自己调研的点做个笔记&#xff0c;持续修改更新中&#xff0c;个人笔记酌情参考。 桌面上半部分包含父子逻辑&#xff1a; Column() { PageDesktopLayout(); } PageDesktopLayout->GridSwiper->Swiper->SwiperPage 1.PageDe…

即插即用篇 | RTDETR引入Haar小波下采样 | 一种简单而有效的语义分割下采样模块

本改进已集成到 RT-DETR-Magic 框架。 下采样操作如最大池化或步幅卷积在卷积神经网络(CNNs)中被广泛应用,用于聚合局部特征、扩大感受野并减少计算负担。然而,对于语义分割任务,对局部邻域的特征进行池化可能导致重要的空间信息丢失,这有助于逐像素预测。为了解决这个问…