Redis 高并发分布式锁实战
目录
环境准备
一 . Redis 安装
二:Spring boot 项目准备
三:nginx 安装
四:Jmeter 下载和配置
案例实战
优化一:加 synchronized 锁
优化二:使用 redis 的 setnx 实现分布式锁
优化三:使用 Lua 脚本 原子删除锁
优化四:使用 Redission 实现分布式锁
环境准备
一 . Redis 安装
1. Redis 下载: https://github.com/tporadowski/redis/releases
2. 解压
3.进入到目录下的cmd,执行如下命令启动 redis
redis-server.exe redis.windows.conf
二:Spring boot 项目准备
1. 引入 Maven 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version></parent><groupId>com.xinxin</groupId><artifactId>cyh</artifactId><version>0.0.1-SNAPSHOT</version><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>19.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.70</version></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.16.5</version></dependency></dependencies></project>
2. 修改 application.properties 配置文件
spring.application.name=cyh spring.redis.host=localhost spring.redis.port=6379
3. 提供 web 服务 Controller
@RestController
public class RedissionController {@Autowiredprivate RedissonClient redisson;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping("/add_stock")public String addStock() {stringRedisTemplate.opsForValue().set("good_stock", "60");return "ok";}@RequestMapping("/sub_stock")public String deductStock() {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("good_stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("good_stock", realStock + "");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}return "ok";}}
4. 分别启动端口为8085和8086 端口的 Spring boot服务
三:nginx 安装
使用 nginx 来进行负载均衡,轮询调用 端口为8085和8086的服务,进行扣减库存。
项目架构图如下:
1. nginx 下载地址:https://nginx.org/en/download.html
2. 解压
3. 在conf 文件中修改 nginx.conf 配置文件
#user nobody;
worker_processes 1;#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;#pid logs/nginx.pid;events {worker_connections 1024;
}http {include mime.types;default_type application/octet-stream;#access_log logs/access.log main;sendfile on;#tcp_nopush on;#keepalive_timeout 0;keepalive_timeout 65;#gzip on;upstream redislock{server 127.0.0.1:8085 weight=1;server 127.0.0.1:8086 weight=1;}server {listen 80;server_name localhost;#charset koi8-r;#access_log logs/host.access.log main;location / {root html;index index.html index.htm;proxy_pass http://redislock;}error_page 500 502 503 504 /50x.html;location = /50x.html {root html;}} }
主要配置了 8085 和 8086 服务的负载均衡
4. 启动nginx 服务,点击以下命令
nginx.exe
在任务管理中,看到nginx就代表启动成功了
四:Jmeter 下载和配置
1. jmeter 下载地址:Apache JMeter - Download Apache JMeter
2.解压
3. 进入 bin 目录,运行 jmeter.bat 文件(jmeter运行环境需要配置JDK环境)
4. 配置 jmeter
1. 新增线程组
设置线程数和循环次数
2. 新增 http 请求
配置nginx的域名端口 和 Spring boot项目的 请求路径
3.新增查看结果树和聚合报告
至此 压测分布式环境搭建完成。
案例实战
初始化 Redis 库存,在浏览器执行下面链接
http://localhost:8085/add_stock
启动 jmeter 进行压测
执行结果如下
从 8085 和 8086 服务的执行日志来看,8085服务中不仅出现重复扣减的问题 ,而且与8086服务中也存在重复扣减库存问题。
优化一:加 synchronized 锁
针对上面的问题,我们通常会加 synchronized 锁,来解决并发问题,修改代码如下
@RequestMapping("/sub_stock1")public String deductStock1() {synchronized (this) {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("good_stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("good_stock", realStock + "");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}}return "ok";}
重启 8085 和 8086 服务,启动 jmeter 再次压测,结果如下
从 8085 和 8086 服务的执行日志来看,同一个服务中不会出现并发问题,但不同服务,比如8085 和8086服务就会出现重复扣减库存的问题。
加 synchronized 锁缺点:在分布式的场景下,还是会出现分布式问题
优化二:使用 redis 的 setnx 实现分布式锁
@RequestMapping("/sub_stock2")public String deductStock2() {String lockKey = "lock_good_stock";String lockValue = UUID.randomUUID().toString();Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);if (!isLock) {return "error";}try {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("good_stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("good_stock", realStock + "");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}} finally {stringRedisTemplate.delete(lockKey);}return "ok";}
注意: 设置值和设置过期时间不能分开写,不然也会出现服务器宕机或者启动,导致锁无法释放的问题 Boolean isLock =stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue); stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
上面代码还存在什么问题呢?
其实,上面的代码在高并发场景下,还是会出现问题,问题是锁过期,当锁过期时间到了之后,则会出现两个问题.
- 下一个线程B会获取到锁,执行扣减库存,导致并发问题
-
上一个线程A执行完时,又把锁释放了,导致下一个线程C又可以获取到锁。
针对锁失效导致的问题,对于第一个问题可以把锁的过期时间调长一点,针对第二个问题,可以先判断是不是自己加的锁,只有自己加的锁才删除。修改代码,如下:
@RequestMapping("/sub_stock3")public String deductStock3() {String lockKey = "lock_good_stock";String lockValue = UUID.randomUUID().toString();Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.MINUTES);if (!isLock) {return "error";}try {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("good_stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("good_stock", realStock + "");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}} finally {if (lockValue.equals(stringRedisTemplate.opsForValue().get(lockKey))) {stringRedisTemplate.delete(lockKey);}}return "ok";}
上面代码看似没问题,但删除锁的代码还是存在问题。判断和删除是两行代码,存在原子性问题。等系统并发变高,系统执行速度变慢,锁可能还是会失效,而判断和删除不是原子性,所以线程A 还是会将线程B 的锁给删除了。在下个优化解决。
优化三:使用 Lua 脚本 原子删除锁
@RequestMapping("/sub_stock4")public String deductStock4() {String lockKey = "lock_good_stock";String lockValue = UUID.randomUUID().toString();Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.MINUTES);if (!isLock) {return "error";}try {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("good_stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("good_stock", realStock + "");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}} finally {final String script ="local lockValue = redis.call('get', KEYS[1])" +"if lockValue == ARGV[1] then " +" redis.call('del', KEYS[1])" +"end";RedisScript<Long> redisScript = new DefaultRedisScript<>(script);stringRedisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue);}return "ok";}
至此一把完善的分布式锁就搞定了。这对于很多中小型公司来说已经够用,但在用户量比较多,并发比较高的公司来锁,可能还是会存在一定问题。比如锁失效的问题,此时可以使用业务比较流行的框架 Redission 来解决。
优化四:使用 Redission 实现分布式锁
@RequestMapping("/sub_stock5")public String deductStock5() {String lockKey = "lock_good_stock";RLock lock = redisson.getLock(lockKey);lock.lock(60, TimeUnit.SECONDS);try {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("good_stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("good_stock", realStock + "");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}} finally {lock.unlock();}return "ok";}
redission 解决了我们上面所有锁提高的问题,包括分布式,原子性和锁失效问题。redission 中有个看门口的机制,当业务还没执行的时候,会不断地给锁续命,是锁不会失效。
相关文章:

Redis 高并发分布式锁实战
目录 环境准备 一 . Redis 安装 二:Spring boot 项目准备 三:nginx 安装 四:Jmeter 下载和配置 案例实战 优化一:加 synchronized 锁 优化二:使用 redis 的 setnx 实现分布式锁 优化三:使用 Lua 脚本…...

关于elementui el-radio 赋值问题
今天遇到这样的问题: 点击的时候,同时选中 照抄官网! 后来发现了问题: 也就是说如果你的版本太低,就不能用value,而得用label,于是修改 <el-radio-group v-model"searchTime"&g…...

2024-11-6----Android 11(全志713m)----- 关于添加 Selinux 权限
需求 节点: /sys/devices/platform/motor0/motor_ctrl上层 APP 使用 JNI 需要对该节点进行 echo 的操作,操作失败。 添加前的验证工作 adb 进去验证下,如下图所示: 发现权限不够。su 以后再操作是OK的,如下图: 添加前的修改 为防止报权限错误,直接给777,因为该…...
shodan5(泷羽sec)
声明 学习视频来自B站UP主 泷羽sec,如涉及侵泷羽sec权马上删除文章。 笔记只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 这节课旨在扩大自己在网络安全方面的知识面,了解网络安全领域的见闻,了…...

【Linux】Ansible集中化运维工具(详解)安装、常用模块、playbook脚本
文章目录 一、Ansible安装及远程控制1、关闭防火墙和SELinux2、安装ansible3、配置SSH无密码登录1、在管理机上生成一对密钥2、将公钥下发到远程主机3、保管密钥 4、主机目录 二、常用模块1、setup模块2、copy模块3、file模块4、shell模块5、script模块6、ping模块7、group模块…...

惠州石湾DELL T130服务器黄灯不开机案例
惠州石湾一个朋友反馈一台DELL PowerEdge T130 塔式服务器故障为 通电后无法开机,前面同时亮3个故障灯。闪电灯,电压灯,高温灯 1:这种情况建议大家更换一个同型号的电源进行故障排除。 2:朋友把该服务器硬件最小化测…...

⭐SmartControl: Enhancing ControlNet for Handling Rough Visual Conditions
目录 0 Abstract 1 Motivation 2 Related Work 2.1 Text-to-Image Diffusion Model 2.2 Controllable Text-to-Image Generation 2.3 ControlNet 2.4 Control Scale Exploration 3 Method 3.1 Framework 3.2 Control Scale Predictor 3.3 Unaligned Data Constructi…...
wordpress站外调用指定ID分类下的推荐内容
在WordPress中,如果你想从站外调用指定ID分类下的推荐内容,你可以使用WordPress REST API来实现。以下是一个基本的步骤指南: 1. 启用REST API 确保你的WordPress站点已经启用了REST API。大多数现代WordPress版本默认启用此功能。 2. 获取…...

Ente: 我们的 Monorepo 经验
原文:manav - 2024.10.29 九个月前,我们切换到了 monorepo。在此,我将介绍我们迄今为止的切换经验。 这并不是一份规范性的建议,而是一个经验的分享,目的是希望能够帮助其他团队做出明智的决策。 与大多数岔路不同&…...
Kafka java 配置
前言: 大家好,大家在springboot项目中,经常采用 KafkaListener 做为消费者。这个是spring为我们封装的。 但是某些情况 注解的方式并不能满足需求。这个时候就需要手动版本了。 介绍: 我们已经集成spring-Kafka 就不需要再…...

网络安全现状:复杂的威胁形势导致压力水平飙升
《2024 年网络安全状况》报告深入分析了当前网络安全挑战和趋势。 该报告重点介绍了几个关键的关注领域,包括人员短缺、技能差距、不断演变的威胁和预算限制,同时还指出了取得进展的领域,例如对威胁响应能力的信心增强以及对网络风险评估的认…...

【机器学习】强化学习(1)——强化学习原理浅析(区分强化学习、监督学习和启发式算法)
文章目录 强化学习介绍强化学习和监督学习比较监督学习强化学习 强化学习的数学和过程表达动作空间序列决策策略(policy)价值函数(value function)模型(model) 强化学习和启发式算法比较强化学习步骤代码走…...

【SoC设计指南 基于Arm Cortex-M】学习笔记1——AMBA
AMBA简介 先进微控制器总线架构(Advanced Microcontroller Bus Architecture,AMBA)是用在arm处理器上的片上总线协议规范集。 AMBA总线协议规范集包含AHB、APB、AXI等。 AHB:先进高性能总线(Advanced High-performance Bus) APB&…...
flutter鸿蒙模拟器 Win环境调试报错问题记录(暂未解决)
前情提要: 1、flutter项目已经正确生成了ohos项目 2、flutter和鸿蒙的环境变量配置正确 3、ohos项目执行flutter build hap成功 4、没有真机,使用win环境创建的x86模拟器 问题状态 使用模拟器运行ohos,控制台提示“安装HAP 报 code:9568347错…...
详解Rust标准库:HashSet
## 查看本地官方文档安装rust后运行 rustup doc查看The Standard Library即可获取标准库内容 std::collections::hash_set::HashSet定义 HashSet是一种集合数据结构,它只存储唯一的元素。它主要用于检查元素是否存在于集合中,或者对元素进行去重操作&…...
记录学习react的一些内容
由于是在公司实际项目中学习,所以不是很完整 需要一点一点的学 1.React.useState 类似于vue中的ref 可以修改状态 但是是异步的 感觉不好用 const [wishData, setWishData] React.useState<any>(null); 只能使用setxxx来修改 2.useEffect(()>{},[]) 类…...

json绘制热力图
首先需要一段热力信息的json,我放在头部了。 然后就是需要de-geo库了。 实现代码如下: import * as d3geo from d3-geoimport trafficJSON from ../assets/json/traffic.jsonlet geoFun;// 地理投影函数// let info {max: Number.MIN_SAFE_INTEGER,mi…...

linux 下查看程序启动的目录
以azkaban为例 第一步、ps -ef | grep azkaban 查询出进程号 第二步、cd /proc/ 第三步 、cd 进程号 第四部 ll 查看详情 查看jar 位置 查看jar 启动命令...

书生浦语第四期基础岛L1G2000-玩转书生「多模态对话」与「AI搜索」产品
文章目录 一、MindSearch二、书生浦语三、书生万象四、进阶任务 一、MindSearch MindSearch 是一个开源的 AI 搜索引擎。它会对你提出的问题进行分析并拆解为数个子问题,在互联网上搜索、总结得到各个子问题的答案,最后通过模型总结得到最终答案。书生浦…...

保护Kubernetes免受威胁:容器安全的有效实践
安全并非“放之四海而皆准”的解决方案,相反地,它更多的是一个范围,受其应用的特定上下文的影响。安全领域的专业人士很少宣称什么产品是完全安全的,但总有方法可以实现更强的安全性。在本文中,我们将介绍各种方法来支…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...