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免受威胁:容器安全的有效实践
安全并非“放之四海而皆准”的解决方案,相反地,它更多的是一个范围,受其应用的特定上下文的影响。安全领域的专业人士很少宣称什么产品是完全安全的,但总有方法可以实现更强的安全性。在本文中,我们将介绍各种方法来支…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
