【Redis】基于Redission实现分布式锁(代码实现)
目录
基于Redission实现分布式锁解决商品秒杀超卖的场景:
1.引入依赖:
2.加上redis的配置:
3.添加配置类:
4.编写代码实现:
5.模拟服务器分布式集群的情况:
1.右键点击Copy Configuration
2.点击Modify option
3. 选择VM option(用于指定新的端口)
4.输入想要指定的端口(比如):-Dserver.port=8082 点击Apply
5.出现新的进程,点击启动,就可以进行分布式多节点测试。
使用Jmeter进行压测
(单机部署)多线程高并发情况下对同一个共享资源进行读写时,会出现数据错乱(数据不一致)的问题;加锁(同步锁)可以解决出现数据不一致的问题;(其他线程进行等待持有锁的线程执行完成后才能进行正常的处理)。
但是随着用户量日益增多,单个服务器压力越来越大,所以使用多个服务器进行分布式集群部署,虽然降低了服务器的压力,提高了服务器的吞吐量。但是还是会出现数据不一致的问题,这时候发现是是同步锁(synchronized)的问题,同步锁基于JVM的,他只能锁住单个服务器中一个线程,但是经过分布式集群部署过后,每台服务器在并发的情况下只能锁住一个线程,所以高并发情况下,还是会出现数据错乱的情况。(假如4个服务器,每台服务器都处于高并发的情况,然后同步锁只能锁住一个线程,这时候每个服务器锁住一个线程,最多可以出现4个线程同时对数据库进行操作,就会造成数据不一致的问题(例如常说的秒杀超卖的情况))
经过查阅资料发现可以通过分布式锁可以解决,然后有三种主流的分布式锁的解决方案分别是使用基于Mysql/Zookeeper/redis实现分布式锁。由于我们系统使用到了Redis,考虑Zookeeper需要重新部署到服务器,避免间接增加服务器的成本,所以直接使用Redis来实现分布式锁。我们发现使用Redis的setNX可以很简单的实现分布式锁。
那么setNX的特性是什么呢?
当一个线程进来,往Redis当中通过setNX去存储一个值的时候,他会根据键值(key)去查看是否存在value值,没有就存储一个值返回true,有就返回一个false,注意一定要加上锁的过期时间,避免线程阻塞。(当用户在请求的过程当中,通过setNX进行加锁完成的时候,这个服务器挂掉了,当其他线程进行setNX进行上锁的时候,发现键当中一直会有值,造成了死锁,其他线程加锁不成功就会造成阻塞)。所以一定要加上过期时间。
随着业务的扩展,又出现了一些问题,就是1.业务的处理时间超过了锁的过期时间和2.线程1可能释放了线程2所持有的锁,【线程1还没将业务处理完成就释放锁,导致线程2拿到锁处理自己的业务。当线程1执行完成后,释放了锁,但是此时线程2已经拿到了当前锁,所以线程1释放的是线程2的锁。】
如何解决这些问题呢?
1.加长锁的过期时间,并增加子线程每10秒去确认线程是否在线。在线则将过期时间重设(续命--他们所说的看门狗);
2.给锁的值设置一个唯一ID(UUID)-(使用setNx进行尝试获取锁的时候,如果获取成功,将锁的值设置为一个唯一的ID,释放锁的时候会拿着key去获取锁的值是否与自己的唯一ID一致,一致才进行释放锁,从而就不会释放其他线程的锁)
上述说这么多,如果让我们自己写起来确实有些麻烦,这时候查阅发现redis他本身已经提供了一个Redission的组件已经解决了这些问题。
那么下面我就提供一个
基于Redission实现分布式锁解决商品秒杀超卖的场景:
springboot版本:2.6.13
redission版本:3.22.0
1.引入依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.22.0</version></dependency>
2.加上redis的配置:
server:port: 8083
spring:redis:host: 127.0.0.1password:port: 6379timeout: 1000
3.添加配置类:
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RedissonConfig {@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private String port;@Beanpublic RedissonClient getRedisson(){Config config = new Config();//单机模式 依次设置redis地址和密码config.useSingleServer().setAddress("redis://" + host + ":" + port).setTimeout(30000); // 设置缓存过期时间为30秒return Redisson.create(config);}
}
4.编写代码实现:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.Objects;@RestController
@RequestMapping("/redisLock")
public class RedisLockController {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate RedissonClient redisson;// 秒杀商品keyprivate static final String REDIS_KEY = "secKillProductKey:";private static final String LOCK_KEY = "secKillProduct";// 秒杀商品总个数private static final int PRODUCT_SIZE = 1000;/*** 初始化秒杀商品总个数到redis中* 注意:测试的时候,先调用这个接口初始化库存*/@GetMapping("/init")public void init() {// 初始化库存 将库存存到redis中stringRedisTemplate.opsForValue().set(REDIS_KEY, String.valueOf(PRODUCT_SIZE));}/*** 秒杀*/@GetMapping("/secKill")public void secKill() {// 获取锁RLock lock = redisson.getLock(LOCK_KEY);try {// 加锁lock.lock();int s = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get(REDIS_KEY)));if (s > 0) {// 扣库存s--;System.out.printf("秒杀商品个数剩余:" + s + "\n");// 更新库存stringRedisTemplate.opsForValue().set(REDIS_KEY, String.valueOf(s));} else {System.out.println("活动太火爆了,商品已经被抢购一空了!");}} catch (Exception e) {System.out.println(Thread.currentThread().getName() + "异常:");e.printStackTrace();} finally {// 释放锁lock.unlock();}}
}
5.模拟服务器分布式集群的情况:
同一个服务不同端口,同时运行两个相同的主程序。
在service中复制一个进程,指定不同端口
1.右键点击Copy Configuration

2.点击Modify option

3. 选择VM option(用于指定新的端口)

4.输入想要指定的端口(比如):-Dserver.port=8082 点击Apply

5.出现新的进程,点击启动,就可以进行分布式多节点测试。

使用Jmeter进行压测
首先需要先调用一下初始化的接口:127.0.0.1:8082/redisLock/init或127.0.0.1:8083/redisLock/init

1.设置线程组

2.两个HTTP请求,请求不同的端口(8082、8083)进行测试高并发多线程的情况:


3.服务器打印结果


经过测试,一秒钟1000个线程同时请求秒杀接收并没有出现超卖的问题。
拓展:
Redis本身是一个CP(一致性和分区容错性)模式的数据库,它通过主从复制实现高可用性,当主节点挂掉时,从节点会自动进行选举,选出一个新的主节点继续提供服务。但是,在主节点挂掉之前,它可能还来不及将最新的数据同步到从节点,这时就会出现数据不一致的问题。
如果redis采用主从模式进行部署,当往redis中通过setNX进行加锁的过程中,主节点挂了,主节点的数据并没有同步到从节点当中,这种怎么办?
可以使用RedLock(红锁)解决,RedLock是一个分布式锁的实现,它可以通过访问多个Redis节点来实现更高的可用性和一致性。RedLock的工作原理是,在加锁时,向多个Redis节点发送请求,只有当所有节点都成功返回时,才认为加锁成功。
相关文章:
【Redis】基于Redission实现分布式锁(代码实现)
目录 基于Redission实现分布式锁解决商品秒杀超卖的场景: 1.引入依赖: 2.加上redis的配置: 3.添加配置类: 4.编写代码实现: 5.模拟服务器分布式集群的情况: 1.右键点击Copy Configuration 2.点击Modi…...
websocket 安全通信
WebSocket 协议 WebSocket:在 2008 年诞生,2011 年成为国际标准。它允许服务器主动向客户端推送信息,客户端也可以主动向服务器发送信息,实现了真正的双向平等对话。它是一种在单个 TCP 连接上进行全双工通讯的协议,能…...
代码生成技术技术-janino
文章目录 背景简单例子flink 例子 背景 代码生成技术适合在需要动态代码编译的场景中使用。比如大数据计算场景下,经常会要把flink sql 转成实际的执行计划 简单例子 代码是一个string 类型,直接用janino 编译后,就可以得到加载到jvm里的c…...
QT事件处理系统之四:自定义事件的注册及拦截、发送
1、自定义事件的注册 自定义事件对象 自定义的事件类必须继承自QEvent,并且无需指定父类 自定义的事件类必须拥有全局唯一的Type值,使用QEvent::User+value即可。 class MyEvent : public QEvent {public:MyEvent();QString getStr() {...
vs2022 studio控制台出现中文乱码解决
vs2022 studio控制台出现中文乱码解决 问题解决 问题 这里cout中间的中文,但控制台出现的是乱码对此需要进行修改 解决 打开运行的主文件,也就是整个程序的入口,对他另存为 之后点击编码保存 接着将编码保存的格式变为图片对应的这种 记…...
支持向量机介绍
一、引言 1.支持向量机(SVM)的概念和背景 支持向量机(Support Vector Machine,简称SVM)是一种起源于统计学习理论的分类和回归算法。20世纪60年代,Vapnik等人提出了支持向量机理论,该理论基…...
电压互感器在线监测的原理
电压互感器在线监测的原理主要基于电磁感应、电场效应以及一系列先进的监测技术。以下是对其原理的详细解释: 一、电磁感应原理 电压互感器(Voltage Transformer,简称VT)本质上是一种降压变压器,它利用电磁感应的原理…...
算法训练与程序竞赛题目集合(L4)
目录 L4-103 就不告诉你 输入格式: 输出格式: 输入样例: 输出样例: L4-104 Wifi密码 输入格式: 输出格式: 输入样例: 输出样例: L4-105 冠军魔术 输入格式: …...
Selenium进行Web自动化测试
Selenium进行Web自动化测试 SeleniumPython实现Web自动化测试一、环境配置 SeleniumPython实现Web自动化测试 一、环境配置 环境基于win10(X64) 安装Python;安装PyCham安装chomedriver chomedriver下载地址 可以查看本地chrome软件版本下载…...
模拟算法讲解
模拟算法是一种基于实际情况模拟的算法,通过模拟现实世界中的系统或过程,来研究它们的性质和行为。模拟算法可以用于解决各种问题,包括物理模拟、经济模拟、社会模拟等。 模拟算法的基本步骤包括: 定义问题:明确需要模…...
Leetcode 3196. Maximize Total Cost of Alternating Subarrays
Leetcode 3196. Maximize Total Cost of Alternating Subarrays 1. 解题思路2. 代码实现 题目链接:3196. Maximize Total Cost of Alternating Subarrays 1. 解题思路 这一题就是一个动态规划,只需要考虑每一个元素作为开始和处于序列当中的二元态即可…...
Elasticsearch**Elasticsearch自定义插件开发入门
Elasticsearch作为一个强大的搜索引擎和数据分析工具,其强大的扩展性是其受欢迎的重要原因之一。自定义插件开发入门** Elasticsearch作为一个强大的搜索引擎和数据分析工具,其强大的扩展性是其受欢迎的重要原因之一。通过自定义插件,用户可…...
在Ubuntu中创建Ruby on Rails项目并搭建数据库
新建Rails项目 先安装bundle Ruby gem依赖项工具: sudo apt install bundle 安装Node.js: sudo apt install nodejs 安装npm 包管理器: sudo apt install npm 安装yarn JavaScript包管理工具: sudo apt install yarn 安装webpacker: …...
微信小程序反编译 2024 unveilr.exe
ps:一开始用的反编译工具是wxappUnpacker,后面改为 unveilr.exe 1.先找到小程序安装目录“E:\聊天记录\WeChat Files\Applet”,要反编译小程序的包 文件夹下的名字对应的是小程序ID,如果不确定是哪个,可以删除->打…...
测试测量-DMM直流精度
测试测量-DMM直流精度 最近去面试,发现了自己许多不足,比如我从未考虑过万用表准或者不准,或者万用表有多准? 在过去的实验室中,常用的DMM有KEYSIGHT 34401A以及 KEITHLEY THD2015,就以这两台为例&#x…...
AGV机器人的调度开发分析(2)- 内核中的调度
我们开发AGV的调度系统,最重要的是要实现调度的运行,那么调度要执行哪些任务呢? 先来看看德国开发的开源openTCS中的功能定义: openTCS的控制核心是kernel,目的是提供运输系统/工厂的抽象驾驶模型,管理运…...
HTTP详细总结
概念 HyperText Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则。 特点 基于TCP协议: 面向连接,安全 TCP是一种面向连接的(建立连接之前是需要经过三次握手)、可靠的、基于字节流的传输层通信协议,在…...
【Android】代码中将 SVG 图像转换颜色
要在代码中将 SVG 图像转换为黑色,你可以使用一个库,例如 AndroidSVG 或 SVG-Android。以下是一个简单的示例代码,展示了如何通过代码方式将 SVG 图像改为黑色: // 导入 AndroidSVG 库 import com.caverock.androidsvg.SVG; impo…...
网络故障排查-TCP标志位
目录 1. SYN(Synchronize) 2. SYN-ACK(Synchronize-Acknowledge) 3. FIN(Finish) 4. RST(Reset) 故障排除步骤 网络流量分析仪中的TCP标志位(SYN、SYN-ACK、FIN、RS…...
[Vue3+Vite+TS] Windows用户设置 VITE_CJS_TRACE=true 标志运行你的脚本来记录堆栈跟踪
Windows用户无法直接在CMD中运行官网提供的命令: VITE_CJS_TRACEtrue vite dev由于 VITE_CJS_TRACE 不是 Vite 配置文件中的标准选项,不能直接写入配置文件进行设置。 但可以使用 Node.js 的 cross-env 包来跨平台设置环境变量. 首先,需要安…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
