当前位置: 首页 > news >正文

Redis学习路线(6)—— Redis的分布式锁

一、分布式锁的模型

(一)悲观锁: 认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行。例如Synchronized、Lock都属于悲观锁。

  • 优点: 简单粗暴
  • 缺点: 性能略低

(二)乐观锁: 认为线程安全问题不一定会发生,因此不加锁,只有在更新数据时判断有没有其他线程对数据做了修改,如果没有修改则认为是安全的,自己才能更新数据;如果已经被其它线程修改,说明发生了安全问题,此时可以重试或异常。

  • 优点: 性能好
  • 缺点: 存在成功率低的问题

(三)常见的实现方式:

  • 版本号法: 通过 id-stock-version结构,通过查询的version与本次是否相同来判断是否被修改。
  • CAS法: 是版本号法的改良版,是用 old-query-new的结构,通过第一次query出来的stock,与第二次提交的stock是否一致,若一致则 old = new;

二、Redis的分布式锁

(一)分布式锁的作用: 作为公用JVM锁监视器,集群中的每台JVM都能获取锁监视器监测的线程,多个JVM内部同步了线程执行。

(二)分布式锁的需求: 多进程可见、互斥、高可用、 高性能、安全性…

(三)常见分布式锁的差异:

MySQLRedisZookeeper
互斥利用mysql本身的互斥锁机制利用setnx这样的互斥命令利用节点的唯一性和有序性实现互斥
高可用
高性能一般一般
安全性断开连接,自动释放锁利用锁超时时间,到期释放临时节点,断开连接自动释放

(四)Redis实现分布式锁

1、获取锁:

  • 互斥: 确保只有一个线程获取锁。SETNX lock thread1

2、释放锁

  • 手动释放: DEL lock
  • 过期释放: EXPIRE lock 5

(1)通过 SET 操作 实现原子性操作: SET lock thread1 EX 10 NX,意思是创建一个lock缓存,值为thread1,保持10s时间,NX为互斥操作

(2)当锁获取失败时的方法:

  • 阻塞式获取锁: 一直等待到有线程释放锁。(对CPU资源消耗高)
  • 非阻塞式获取锁: 失败就不再尝试获取锁。

3、代码实现分布式锁

(1)需求: 定义一个类,实现Redis分布式锁功能。

public class SimpleRedisLock implements ILockService {private static final String LOCK = "lock:";private String threadName;private StringRedisTemplate redisTemplate;public SimpleRedisLock() {}public SimpleRedisLock(String threadName, StringRedisTemplate redisTemplate) {this.threadName = threadName;this.redisTemplate = redisTemplate;}@Overridepublic boolean tryLock(long timeoutSec) {//1、获取锁Boolean absent = redisTemplate.opsForValue().setIfAbsent(LOCK + threadName, String.valueOf(Thread.currentThread().getId()), timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(absent);}@Overridepublic void unlock() {//2、解锁redisTemplate.delete(LOCK + threadName);}
}

四、基于Redis的分布式锁优化——Redisson对象

(一)基于setnx实现的分布式锁存在的问题

  • 不可重入: 同一个线程无法多次获得同一把锁
  • 不可重试: 获取锁只尝试一次就返回false,没有重试机制
  • 超时释放锁: 锁超时释放,虽然可以避免死锁,但在业务耗时过长,也会导致锁释放,存在安全隐患。
  • 主从一致性: 如果Redis提供了主从集群,主从同步存在延迟,当主宕机时,如果从同步主中的所数据,则会出现锁实现。

(二)实现分布式锁的常用对象——Redisson

1、概念: Redisson是一个Redis的基础上实现的Java驻内存数据网络(In-Memory Data Grid)。提供了一系列分布式的Jav常用对象,还提供了许多分布式服务,其中包含了各种分布式锁的实现。

2、分布式锁的种类 官网地址: https://redisson.org

  • 分布式锁(Lock)和同步器(Synchronizer)
    • 可重入锁(Reentrant Lock)
    • 公平锁(Fair Lock)
    • 联锁(Multi Lock)
    • 红锁(Red Lock)
    • 读写锁(ReadWrite Lock)
    • 信号量(Semaphore)
    • 可过期性信号量(PermitExpirableSemaphore)
    • 闭锁(CountDownLatch)

3、Redisson的基本使用

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version>
</dependency>
@Configuration
public class RedisConfig {@Beanpublic RedissonClient redissonClient() {//配置类Config config = new Config();//连接redisconfig.useSingleServer().setAddress("redis://192.168.92.131:6379").setPassword("123321");//创建客户端return Redisson.create(config);}
}
@Resource
private RedissonClient redissonClient;@Test
void testRedisson() throws InterruptedException {//	获取锁(可重入),指定锁名称RLock lock = redissonClient.getLock("anyLock");//	尝试获取锁,参数分别是: 获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);//	判断释放获取成功if(isLock){try{System.out.println("执行业务");}finally{//释放锁lock.unlock();}}
}

4、Redisson可重入锁原理

(1)可重入锁是什么?

可重入锁是指一个线程可以多次获取一把锁的锁获取机制。

(2)ReentrantLock可重入原理

当线程获取锁时,如果有线程占用锁,就检查该线程是否是自己本身,若是自己则再一次尝试获取锁,每执行一次尝试获取锁,就会有个重入计数器记录线程重入的次数,在本次方法执行结束后释放锁,重入计数器相应减一,直到整个方法执行完,才能完整的将锁释放掉。

(3)基于Redis的可重入锁的实现方式

流程: 判断锁是否存在(若不存在,则获取锁并添加线程标识,设置锁有效期,执行业务,执行完毕后依旧需要判断锁的归属以及锁计数状态) 》判断锁标识是否是自己(若是,则锁计数+1) 》 若不是,获取锁失败 》

lua脚本

local key = KEYS[1];
local threadId = ARGV[1];
local releaseTime = ARGV[2];--1、判断当前锁是否是自己
if (redis.call("HEXISTS", key, threadId) == 0) then-- 不是,则直接返回return nil;
end
-- 如果是,则计数器-1
local count = redis.call("HINCRBY", key, threadId, -1);--2、判断统计数是否为0
if (count > 0) then-- 统计数不为零,则重置计时器redis.call("expire", key, releaseTime);
else--统计数为零,则释放锁redis.call("del", key);return nil;
end

5、multiLock,主从一致性

(1)产生原因: 多台Redis,主节点主要存储最新的数据,从节点需要同步数据,在数据同步过程中,会产生延时,因为某些异常导致了主节点宕机了,从节点的数据就不一致并且因为锁对象锁定的redis宕机了,所以锁就失效了,产生了之前所提到的所有分布式安全问题。

(2)解决方法: 联锁机制。

(3)联锁机制的主从一致性的解决方式: 通过获取Redis集群的所有锁,只有获取了Redis集群的所有锁才能进行数据更新。

(4)Redssion的联锁: MultiLock

6、Redisson的锁重试以及WatchDog机制

(1)Redisson的锁重试机制的基本流程: 可自行查看RedissonLock类的重试锁机制和释放锁机制

  • 将等待时间,释放时间,统一转换为毫秒级
  • 第一次尝试获取锁(若返回的TTL为null,说明获取到了)
    • 判断是否获取锁(阻塞等待结果返回)
      • 判断锁释放时间是否为默认(传参了-1,表示默认),若为默认则使用WatchDog监控时间(30s),否则以传入的参数为准
      • 使用异步释放函数,函数里包含了可重入锁实现的脚本(结果返回到一个Future类)
  • 若未获取到锁,则将等待时间减去获取锁的那段时间,并且加以判断等待时间是否不足(若不足,则放弃重试返回错误信息)
  • 订阅当前尝试获取锁的线程标识(阻塞等待剩余等待时间,若还是没有释放锁的信号,则取消订阅并返回错误信息),当释放锁脚本中publish命令执行后,开始进行锁获取
  • 订阅到锁信号
    • 判断等待时间是否充足(等待时间先被减去等待信号的时间,计算结果若小于零,则返回错误信息)
    • 若充足,则循环尝试获取锁,直至锁成功获取,或等待时间不足返回错误(循环尝试并不是一直循环,只有在获取到锁信号的时候才会尝试获取)

在这里插入图片描述


三、分布式锁使用过程中的相关问题

(一)数据超量修改

1、产生原因: 由于多线程的参与,功能模块的方法之间执行顺序就会有差异,那么当多个线程由于操作顺序不同可能都查询到了库存还有剩余,都会去执行扣减库存的操作,这样原本库存数 < 请求线程数,就造成了库存直接变负的情况。

2、解决方案: 加锁,保持用户访问时持有锁才能修改操作。

3、加乐观锁和加悲观锁对问题解决效果

(1)加乐观锁: 由于是通过对更新前后数据变化进行判断是否能够更新数据,同时访问的线程,在线程 a 访问更新后,线程 b 由于更新前后访问的数据不一致,导致线程更新失败,同时在同一时期访问的线程全部失败,所以它的效率会极差,但依旧可以完成业务。

(2)加悲观锁: 由于线程 a 获得了锁,进入了访问更新阶段,但线程 b 并未获取锁而阻塞,若因为没有锁重试机制,可能会导致大量线程失败,相较于乐观锁的方式,成功率显然有所提升,并且其安全性也获得了提升,不会因为同一个用户的多次相同请求而多次更新。

(二)集群状态下,锁功能失效

1、产生原因: JVM内部维护了一个锁监视器,在同一个userid下,认为这个线程是同一个线程,但是当有两个或更多的JVM集群出来,而锁监视器并没有锁定同一个线程,所以才会有并发安全问题。

2、解决方案: 采用分布式锁

(三)业务阻塞导致锁超时释放

1、原因: 线程被阻塞,分布式锁超时被释放,导致线程运行混乱。

2、解决方法: 在业务完成后,先检查锁的标识是否一致,再判断是否释放锁。

(四)超时释放锁

1、产生原因: 由于JVM的垃圾回收机制,线程在释放锁之前可能会遭遇阻塞,造成超时释放锁

2、解决方法: 将判断表示与释放锁形成原子性。

3、实现方法: 使用Lua脚本,编写多条Redis,保证Redis命令的原子性。

3、Lua脚本的使用方法: Redis提供了一个回调函数,可以调用脚本。

EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 name Rose

4、释放锁的业务流程

  • 获取锁中的线程标识
  • 判断是否与指定标识(当前线程标识)一致
  • 如果一致则释放锁(删除)
  • 如果不一致则什么都不做

相关文章:

Redis学习路线(6)—— Redis的分布式锁

一、分布式锁的模型 &#xff08;一&#xff09;悲观锁&#xff1a; 认为线程安全问题一定会发生&#xff0c;因此在操作数据之前先获取锁&#xff0c;确保线程串行执行。例如Synchronized、Lock都属于悲观锁。 优点&#xff1a; 简单粗暴缺点&#xff1a; 性能略低 &#x…...

一、创建自己的docker python容器环境;支持新增python包并更新容器;离线打包、加载image

1、创建自己的docker python容器环境 参考&#xff1a;https://blog.csdn.net/weixin_42357472/article/details/118991485 首先写Dockfile&#xff0c;注意不要有txt等后缀 Dockfile # 使用 Python 3.9 镜像作为基础 FROM python:3.9# 设置工作目录 WORKDIR /app# 复制当前…...

【Git】git企业开发命令整理,以及注意点

1.git企业开发过程 业务的分支大概有以下几个&#xff1a; master&#xff1a;代码随时可能上线 develop&#xff1a;代码最新 feature/xxx&#xff1a;实际业务开发分支 release/xxx&#xff1a;预发布分支 fix&#xff1a;修复bug分支 过程大概是这样的&#xff1a; 首…...

使用Django自带的后台管理系统进行数据库管理的实例

Django自带的后台管理系统主要用来对数据库进行操作和管理。它是Django框架的一个强大功能&#xff0c;可以让你快速创建一个管理界面&#xff0c;用于管理你的应用程序的数据模型。 使用Django后台管理系统&#xff0c;你可以轻松地进行以下操作&#xff1a; 数据库管理&…...

leetcode解题思路分析(一百四十五)1254 - 1266 题

统计封闭岛屿的数目 二维矩阵 grid 由 0 &#xff08;土地&#xff09;和 1 &#xff08;水&#xff09;组成。岛是由最大的4个方向连通的 0 组成的群&#xff0c;封闭岛是一个 完全 由1包围&#xff08;左、上、右、下&#xff09;的岛。请返回 封闭岛屿 的数目。 BFS或者DFS…...

使用 GORM 连接数据库并实现增删改查操作

步骤 1&#xff1a;安装 GORM 首先&#xff0c;我们需要安装 GORM 包。在终端中运行以下命令&#xff1a; shell go get -u gorm.io/gorm 步骤 2&#xff1a;导入所需的包 在 Go 代码的开头导入以下包&#xff1a; import ("gorm.io/driver/mysql" // 如果你使用…...

kafka集群搭建(Linux环境)

zookeeper搭建&#xff0c;可以搭建集群&#xff0c;也可以单机&#xff08;本地学习&#xff0c;没必要搭建zookeeper集群&#xff0c;单机完全够用了&#xff0c;主要学习的是kafka&#xff09; 1. 首先官网下载zookeeper&#xff1a;Apache ZooKeeper 2. 下载好之后上传到…...

树莓派本地快速搭建web服务器,并发布公网访问

文章目录 树莓派本地快速搭建web服务器&#xff0c;并发布公网访问 树莓派本地快速搭建web服务器&#xff0c;并发布公网访问 随着科技的发展&#xff0c;电子工业也在不断进步&#xff0c;我们身边的电子设备也在朝着小型化和多功能化演进&#xff0c;以往体积庞大的电脑也在…...

集合中的数据结构

栈 先进后出入口跟出口在同一侧 队列 先进先出入口跟出口在不同的一层 数组 查询快、增删慢查询快是因为数组的地址是连续的&#xff0c;我们通过数组的首地址就可以找到数组&#xff0c;之后通过数组的下标就可以访问数组的每一个元素。增删慢是因为数组的长度是固定的&…...

CentOS 8 错误: Error setting up base repository

配置ip、掩码、网关、DNS VMware网关可通过如下查看 打开网络连接 配置镜像的地址 vault.centos.org/8.5.2111/BaseOS/x86_64/os/...

java外观模式

在Java中&#xff0c;外观模式&#xff08;Facade Design Pattern&#xff09;用于为复杂的子系统提供一个简单的接口&#xff0c;以方便客户端的使用。外观模式是一种结构型设计模式&#xff0c;它隐藏了系统的复杂性&#xff0c;将多个类的复杂操作封装在一个外观类中&#x…...

3秒快速打开 jupyter notebook

利用 bat 脚本&#xff0c;实现一键打开 minconda 特点&#xff1a; 1、可指定 python 环境 2、可指定 jupyter 目录 一、配置环境 minconda 可以搭建不同的 python 环境&#xff0c;所以我们需要找到 minconda 安装目录&#xff0c;把对应目录添加到电脑环境 PATH 中&#…...

数据安全

数据的备份与恢复 1. 数据备份技术 任何数据在长期使用过程中&#xff0c;都存在一定的安全隐患。由于认为操作失误或系统故障&#xff0c;例如认为错误、程序出错、计算机失效、灾难和偷窃&#xff0c;经常造成数据丢失&#xff0c;给个人和企业造成灾难性的影响。在这种情况…...

华为nat64配置

1.前期环境准备 环境拓扑 拓扑分为两个区域,左边为trust区域,使用IPv4地址互访,右边为untrust区域,使用IPv6地址互访 2.接口地址配置 pc1地址配置 pc2地址配置 FW接口配置 (1)首先进入防火墙配置界面 注:防火墙初始账号密码为user:admin,pwd:Admin@123,进入之后…...

从分片传输到并行传输之大文件传输加速技术

随着大文件的传输需求越来越多&#xff0c;传输过程中也会遇到很多困难&#xff0c;比如传输速度慢、文件安全性低等。为了克服这些困难&#xff0c;探讨各种大文件传输加速技术。其中&#xff0c;分片传输和并行传输是两种比较常见的技术&#xff0c;下面将对它们进行详细说明…...

mybatisPlus入门篇

文章目录 初窥门径1.1 初识MybatisPlus1.2 MybatisPlus的特性1.3 MybatisPlus的架构模型 入门案例2.1 准备相关开发环境2.2 搭建springboot工程2.3 创建数据库2.4 引入相关依赖2.5 创建实体类2.6 集成MybatisPlus2.7 单元测试2.8 springboot日志优化 初窥门径 1.1 初识Mybatis…...

NineData支持最受欢迎数据库PostgreSQL

根据在 Stack Overflow 发布的 2023 开发者调研报告中显示&#xff0c;PostgreSQL 以 45% vs 41% 的受欢迎比率战胜 MySQL&#xff0c;成为新的最受欢迎的数据库。NineData 也在近期支持了 PostgreSQL&#xff0c;用户可以在 NineData 平台上进行创建数据库/Schema、管理用户与…...

Redis配置类

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…...

【前端知识】React 基础巩固(三十六)——RTK中的异步操作

React 基础巩固(三十六)——RTK中的异步操作 一、RTK中使用异步操作 引入RTK中的createAsyncThunk&#xff0c;在extraReducers中监听执行状态 import { createSlice, createAsyncThunk } from "reduxjs/toolkit"; import axios from "axios";export cons…...

33. 本地记事本

本地记事本 html部分 <button class"add"><i class"iconfont icon-jiahao"></i> </button>css部分 *{margin: 0;padding: 0; } body{background-color: #7bdaf3;display: flex;padding-top: 3rem;flex-wrap: wrap; } .add{pos…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

STM32---外部32.768K晶振(LSE)无法起振问题

晶振是否起振主要就检查两个1、晶振与MCU是否兼容&#xff1b;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容&#xff08;CL&#xff09;与匹配电容&#xff08;CL1、CL2&#xff09;的关系 2. 如何选择 CL1 和 CL…...

离线语音识别方案分析

随着人工智能技术的不断发展&#xff0c;语音识别技术也得到了广泛的应用&#xff0c;从智能家居到车载系统&#xff0c;语音识别正在改变我们与设备的交互方式。尤其是离线语音识别&#xff0c;由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力&#xff0c;广…...

面试高频问题

文章目录 &#x1f680; 消息队列核心技术揭秘&#xff1a;从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"&#xff1f;性能背后的秘密1.1 顺序写入与零拷贝&#xff1a;性能的双引擎1.2 分区并行&#xff1a;数据的"八车道高速公路"1.3 页缓存与批量处理…...

手动给中文分词和 直接用神经网络RNN做有什么区别

手动分词和基于神经网络&#xff08;如 RNN&#xff09;的自动分词在原理、实现方式和效果上有显著差异&#xff0c;以下是核心对比&#xff1a; 1. 实现原理对比 对比维度手动分词&#xff08;规则 / 词典驱动&#xff09;神经网络 RNN 分词&#xff08;数据驱动&#xff09…...