Redis分布式锁的实现和分析

news2024/12/23 18:23:33

关于 synchronized

项目单独部署时,使用 synchronized 可以实现并发安全,但如果项目搭建集群时,有多个线程同时对某项目中的数据修改时,可能会出现并发问题。

synchronized 关键字底层是 JVM 中的 monitor(管程,监视器)管理的。一个JVM对应一个 monitor 。所以在分布式项目中,不同的环境下,synchronized 对应的 monitor 可能不同。

关于 synchronized 底层原理的理解见 JUC并发编程——对于synchronized关键字的理解

分布式锁的实现

使用 setnx(SET if Not eXist) 可以实现分布式锁

SETNX :如果 key 不存在,设置 value。如果 key,设置 value 失败。

但是只使用 setnx 命令,存在线程安全问题。

解决方法:SET key value NX EX seconds

存在的问题:超时释放锁导致误删其他锁。

如果线程1先获取到了锁,由于一些原因线程1阻塞,一段时间后Redis 超时释放了锁,
线程2获取锁成功,还未到过期时间,此时线程1恢复运行,删除了线程2的锁,
线程2非自然释放后,线程3获取锁成功,执行业务。
在这里插入图片描述

解决方法:在原有的代码中获取锁时,存入线程标识,在释放锁前判断标识是否是自己

存在的问题:无法保证判断锁标识和释放锁的原子性

线程1在判断锁标识成功后,由于一些原因进入阻塞(如jvm 的 full gc 会stw),在阻塞这段时间内,释放了线程1的锁,线程2获取锁,线程1醒来后已经判断过是否是自己锁,所以会直接释放线程2的锁。

在这里插入图片描述

解决方法:Redis 调用 lua 脚本

-- 比较线程标识与锁中的标识是否一致
if (redis.call('get',KEYS[1]) == ARGV[1]) then
    -- 一致 释放锁
    return redis.call('del',KEYS[1])
end
-- 不一致 直接返回
return 0	
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;

   /**
    * 让 lua 脚本在类加载时加载
    */
   static {
       UNLOCK_SCRIPT = new DefaultRedisScript<>();
       UNLOCK_SCRIPT.setLocation(new ClassPathResource("FreeLock.lua"));
       UNLOCK_SCRIPT.setResultType(Long.class);
   }
   /**
    * Redis 支持lua 脚本功能,在一个脚本中编写多条Redis命令,确保命令执行时的原子性
    */
   @Override
   public void unlock() {
       // 调用 lua 脚本
       stringRedisTemplate.execute(
               UNLOCK_SCRIPT,
               Collections.singletonList(KEY_PREFIX + name),
               ID_PREFIX + Thread.currentThread().getId());
   }

存在的问题:不可重入

解决方法:使用 Redisson 实现分布式锁

  1. 引入 Redisson 依赖
    <dependency>
    	<groupId>org.redisson</groupId>
    	<artifactId>redisson</artifactId>
    	<version>3.13.6</version>
    </dependency>
    
  2. 配置 Redisson 客户端
    @Configuration
    public class RedissonConfig {
        @Autowired
        private RedisConfigProperties redisConfigProperties;
    
        @Bean
        public RedissonClient redissonClient() {
            // 配置
            Config config = new Config();
            // 设置单节点的redis
            config.useSingleServer()
                    .setAddress("redis://" + redisConfigProperties.getHost() + ":" + redisConfigProperties.getPort())
                    .setPassword(redisConfigProperties.getPassword());
            // 创建 Redisson 对象
            return org.redisson.Redisson.create(config);
        }
    }
    
  3. 在业务中使用 Redisson 分布式锁
    @Autowired
    private RedissonClient redissonClient;
    
    public void testRedisson throw Exception {    
        RLock lock = redissonClient.getLock("lock:order:" + userId);
        /**
         * 通过 Redisson 获取分布式锁
         */
        boolean isLock = lock.tryLock();
        if (!isLock){
    		log.debug("执行业务");
        }
        try {
    		log.debug("执行业务");
        } finally {
            lock.unlock();
        }
    }    
    

存在的问题:可能出现主从不一致

在 Redis 集群中,如果Redis 主机宕机,在进行故障转移时,Redis 中主从同步是异步的,所以可能在 Redis 主机中还没有将 实现分布式锁的 key 传给副本,Redis 主机就宕机了,这时另一台客户端访问 Redis 时就访问到了主机实现分布式锁中的资源。所以这是不安全的😱。
在这里插入图片描述

解决方法:使用 Redisson 获取 Redis 集群中的所有锁,进行联锁。

为了实现主从一致性,获取Redis 集群中的所有锁,进行联合。删除锁和获取锁都会同时在 Redis 集群中创建,且进行联锁的 Redis 服务,不存在主从关系(可以都认为是主节点),且支持重入,超时释放,可重试,主从一致。

Redisson 可重入锁原理

与 juc 中的 ReentrantLock 相比,
ReentrantLock 底层实现可重入的原理是是实现了 AbstractQueuedSynchronizer(AQS),通过修改锁的状态 state 控制锁的释放,保证了可重入。获取锁 state++,释放锁 state–,当state == 0时,就真正释放锁。

Redisson 底层使用 Redis 的哈希结构存储,state 作为 hash 字段的 value,从而进行自增或自减。
在这里插入图片描述
设置锁的有效期时,如果没有指定有效期,默认 leaseTime == -1,开启 watchDog 机制,重复更新锁的有效期(默认是30秒)。指定有效期则不使用 watchDog 机制。

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

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

相关文章

【示例】Spring-IOC理解

前言 本文从常规的开发示例&#xff08;DAO、Service、Client&#xff09;入手&#xff0c;逐步体会理解IOC的原理及实现。 文中示例的代码地址&#xff1a; GitHubhttps://github.com/Web-Learn-GSF/Java_Learn_Examples父工程Java_Framework_Spring 示例 | 常规三层开发示…

stm32 之SPI通信协议

本文为大家介绍 SPI 通信协议的基础知识。 文章目录 前言一、SPI协议的概念二、SPI总线架构三、SPI通讯时序1. 起始&#xff0c;停止 信号2.CPOL&#xff08;时钟极性&#xff09;/CPHA&#xff08;时钟相位&#xff09; 四&#xff0c; I2C 总线 和SPI 总线比较相同点&#xf…

Flutter仿Boss-6.底部tab切换

效果 实现 图片资源采用boss包中的动画webp资源。Flutter采用Image加载webp动画。 遇到的问题 问题&#xff1a;Flutter加载webp再次加载无法再次播放动画问题 看如下代码&#xff1a; Image.asset(assets/images/xxx.webp,width: 40.w,height: 30.w, )运行的效果&#xf…

从二维数组到一维数组——探索01背包问题的动态规划优化

文章目录 题目前知背包问题 二维dp数组一、思路二、解题方法三、Code 一维dp数组一、思路二、解题方法三、Code 总结 本文将继续上一篇博客爬楼梯之后继续讲解同样用到了动态规划的 01背包问题 在解决动态规划问题时&#xff0c;我们经常面临着空间复杂度的挑战。01背包问题是…

前端三剑客 —— JavaScript (第二节)

目录 内容回顾 数据类型 基本数据类型&#xff1a; 引用数据类型&#xff1a; 常见运算 算术运算符 比较运算符 逻辑运算符 赋值运算符 自增/减运算符 三目运算符 位运算符 内容回顾 1.概述 2.基本数据 1.使用方式&#xff08;行内、页面、外部&#xff09; 2.对话框…

通信安全之数据加密

数据安全的需求如今越来越重要&#xff0c;本篇简单举例给日常的TCP/UDP通信加密&#xff0c;至少能让想干坏事的崽犯罪的成本更高一些&#xff08;如果会一些BPF的&#xff0c;可能难不住这些崽&#xff09;&#xff0c;能让我们的数据更安全一点。 经典TCP socket编程 下面…

佑雅的小布谷数据平台获取token如何实现

小博股数据开放平台是面向全部用户的股票数据开放平台&#xff0c;通过调用接口可以获取股票的历史数据。在调用之前需要进行下面的准备工作&#xff0c;第一步注册&#xff1a; 用户在注册之后&#xff0c;登录点击头像进入个人中心&#xff0c;在功能模块的最下方有一个创建应…

IntelliJ IDEA 2024.1安装与激活[破解]

一&#xff1a;IDEA官方下载 ①如题&#xff0c;先到IDEA官方下载&#xff0c;简简单单 ②IDEA官方&#xff1a;IntelliJ IDEA – the Leading Java and Kotlin IDE 二&#xff1a;获取脚本 &#x1f31f;网盘下载&#xff1a;jetbra (密码&#xff1a;lzh7) &#x1f31f;获取…

51单片机入门_江协科技_25~26_OB记录的笔记_蜂鸣器教程

25. 蜂鸣器 25.1. 蜂鸣器介绍 •蜂鸣器是一种将电信号转换为声音信号的器件&#xff0c;常用来产生设备的按键音、报警音等提示信号 •蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器&#xff08;开发板上用的无源蜂鸣器&#xff09; •有源蜂鸣器&#xff1a;内部自带振荡源&a…

报文 消息

报文消息域 MsgField name 域名称 length 长度 fillChar 填充字符 fillSide 填充位置 报文消息片 MsgPiece 由多个消息域按一定的顺序组成 private List<MsgField> itemList new LinkedList<~>();组装消息 报文消息包 MsgPackage 由多个消息片组成 String[]…

FreeRTOS启动任务调度器

FreeRTOS启动任务调度器 这部分内容就要去深入了解源码以及熟悉汇编语言的操作。依旧正点原子的视频。下面首先看开启任务调度器这部分源码&#xff1a; 1开启任务调度器 任务调度器用于启动任务调度器&#xff0c;任务调度器启动后&#xff0c; FreeRTOS 便会开始进行任务调…

MyBatis 应用的组成

王有志&#xff0c;一个分享硬核Java技术的互金摸鱼侠 加入Java人的提桶跑路群&#xff1a;共同富裕的Java人 大家好&#xff0c;我是王有志。在上一篇文章的最后&#xff0c;我们写了一个简单的例子&#xff0c;今天我们就通过这个例子来看一看一个标准的 MyBatis 应用程序由哪…

PS入门|如何使用“主体”功能进行抠图?

前言 前段时间讲到给各种图标和LOGO抠图的办法&#xff0c;分别使用的是 钢笔工具蒙版 PS入门&#xff5c;规规矩矩的图形怎么抠出来&#xff1f; 魔棒工具蒙版 PS入门&#xff5c;黑白色的图标怎么抠成透明背景 色阶蒙版 PS入门&#xff5c;目标比较复杂&#xff0c;但背景…

HTML+CSS+JS复习回顾

环境搭建 下载VScode&#xff0c;依次下载插件&#xff1a;HTML CSS support、Live Server、Auto Rename Tag 一、HTML篇 HTML通过一系列的标签&#xff08;元素&#xff09;来定义文本、图像、链接等。HTML标签是由尖括号包围的关键字。标签通常成对出现&#xff0c;包括开…

在Spring中使用Redis

端口怎么设置&#xff0c;看我前一篇文章 前面使用jedis&#xff0c;通过Jedis对象中各种方法来操作redis的。 此处Spring中则是通过StringRedisTemplate来操作redis。 最原始提供的类是RedisTemplate StringRedisTemplate是RedisTemplate的子类&#xff0c;专门处理文本数据的…

2014最新AIGC创作系统ChatGPT网站源码+AI绘画网站源码+GPT4-All联网搜索模型

一、文章前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持…

无重复的最长字串

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 问题 给定一个字符串&#xff0c;我们需要找到该字符串中的最长无重复子串的长度。 示例 让我们以一个具体的示例来说明这个问题&#…

四、书城开发--3、书城图书部分的开发

书城图书部分 首先我们做书城首页搜索栏下面的图片展示 我们在书城首页组件中通过home请求方法中获取回来的数据中&#xff0c;打印出来可以看到那个banner就是我们现在要的图片 我们在data中定义一个变量banner用来存放获取回来的数据中的banner 然后把它展示出来就可以了&a…

B. Burning Midnight Oil Codeforces Round 112 (Div. 2)

题目链接&#xff1a; Problem - 165B - Codeforceshttps://codeforces.com/problemset/problem/165/B 题目大意&#xff1a; 最后写了至少n个&#xff0c;每次衰减k倍&#xff08;/k&#xff09;&#xff0c;问最初的v最小为多少。 思路&方法&#xff1a; 二分答案。 AC代…

想要品牌传播有效,先清楚这三个本质问题

在互联网时代&#xff0c;企业想要提高市场竞争力就需要做好品牌传播。然而有许多企业在做品牌传播时都会踩坑&#xff0c;原因是因为忽视了这三点&#xff0c;接下来就让媒介盒子和大家分享&#xff1a; 一、 文案本质是“购买理由” 在文案技巧中经常会出现一些词&#xff…