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

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 安装 二&#xff1a;Spring boot 项目准备 三&#xff1a;nginx 安装 四&#xff1a;Jmeter 下载和配置 案例实战 优化一&#xff1a;加 synchronized 锁 优化二&#xff1a;使用 redis 的 setnx 实现分布式锁 优化三&#xff1a;使用 Lua 脚本…...

关于elementui el-radio 赋值问题

今天遇到这样的问题&#xff1a; 点击的时候&#xff0c;同时选中 照抄官网&#xff01; 后来发现了问题&#xff1a; 也就是说如果你的版本太低&#xff0c;就不能用value&#xff0c;而得用label&#xff0c;于是修改 <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权马上删除文章。 笔记只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 这节课旨在扩大自己在网络安全方面的知识面&#xff0c;了解网络安全领域的见闻&#xff0c;了…...

【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 塔式服务器故障为 通电后无法开机&#xff0c;前面同时亮3个故障灯。闪电灯&#xff0c;电压灯&#xff0c;高温灯 1&#xff1a;这种情况建议大家更换一个同型号的电源进行故障排除。 2&#xff1a;朋友把该服务器硬件最小化测…...

⭐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中&#xff0c;如果你想从站外调用指定ID分类下的推荐内容&#xff0c;你可以使用WordPress REST API来实现。以下是一个基本的步骤指南&#xff1a; 1. 启用REST API 确保你的WordPress站点已经启用了REST API。大多数现代WordPress版本默认启用此功能。 2. 获取…...

Ente: 我们的 Monorepo 经验

原文&#xff1a;manav - 2024.10.29 九个月前&#xff0c;我们切换到了 monorepo。在此&#xff0c;我将介绍我们迄今为止的切换经验。 这并不是一份规范性的建议&#xff0c;而是一个经验的分享&#xff0c;目的是希望能够帮助其他团队做出明智的决策。 与大多数岔路不同&…...

Kafka java 配置

前言&#xff1a; 大家好&#xff0c;大家在springboot项目中&#xff0c;经常采用 KafkaListener 做为消费者。这个是spring为我们封装的。 但是某些情况 注解的方式并不能满足需求。这个时候就需要手动版本了。 介绍&#xff1a; 我们已经集成spring-Kafka 就不需要再…...

网络安全现状:复杂的威胁形势导致压力水平飙升

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

【机器学习】强化学习(1)——强化学习原理浅析(区分强化学习、监督学习和启发式算法)

文章目录 强化学习介绍强化学习和监督学习比较监督学习强化学习 强化学习的数学和过程表达动作空间序列决策策略&#xff08;policy&#xff09;价值函数&#xff08;value function&#xff09;模型&#xff08;model&#xff09; 强化学习和启发式算法比较强化学习步骤代码走…...

【SoC设计指南 基于Arm Cortex-M】学习笔记1——AMBA

AMBA简介 先进微控制器总线架构&#xff08;Advanced Microcontroller Bus Architecture&#xff0c;AMBA&#xff09;是用在arm处理器上的片上总线协议规范集。 AMBA总线协议规范集包含AHB、APB、AXI等。 AHB&#xff1a;先进高性能总线(Advanced High-performance Bus) APB&…...

flutter鸿蒙模拟器 Win环境调试报错问题记录(暂未解决)

前情提要&#xff1a; 1、flutter项目已经正确生成了ohos项目 2、flutter和鸿蒙的环境变量配置正确 3、ohos项目执行flutter build hap成功 4、没有真机&#xff0c;使用win环境创建的x86模拟器 问题状态 使用模拟器运行ohos&#xff0c;控制台提示“安装HAP 报 code:9568347错…...

详解Rust标准库:HashSet

## 查看本地官方文档安装rust后运行 rustup doc查看The Standard Library即可获取标准库内容 std::collections::hash_set::HashSet定义 HashSet是一种集合数据结构&#xff0c;它只存储唯一的元素。它主要用于检查元素是否存在于集合中&#xff0c;或者对元素进行去重操作&…...

记录学习react的一些内容

由于是在公司实际项目中学习&#xff0c;所以不是很完整 需要一点一点的学 1.React.useState 类似于vue中的ref 可以修改状态 但是是异步的 感觉不好用 const [wishData, setWishData] React.useState<any>(null); 只能使用setxxx来修改 2.useEffect(()>{},[]) 类…...

json绘制热力图

首先需要一段热力信息的json&#xff0c;我放在头部了。 然后就是需要de-geo库了。 实现代码如下&#xff1a; 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 搜索引擎。它会对你提出的问题进行分析并拆解为数个子问题&#xff0c;在互联网上搜索、总结得到各个子问题的答案&#xff0c;最后通过模型总结得到最终答案。书生浦…...

保护Kubernetes免受威胁:容器安全的有效实践

安全并非“放之四海而皆准”的解决方案&#xff0c;相反地&#xff0c;它更多的是一个范围&#xff0c;受其应用的特定上下文的影响。安全领域的专业人士很少宣称什么产品是完全安全的&#xff0c;但总有方法可以实现更强的安全性。在本文中&#xff0c;我们将介绍各种方法来支…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

ip子接口配置及删除

配置永久生效的子接口&#xff0c;2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

作为测试我们应该关注redis哪些方面

1、功能测试 数据结构操作&#xff1a;验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化&#xff1a;测试aof和aof持久化机制&#xff0c;确保数据在开启后正确恢复。 事务&#xff1a;检查事务的原子性和回滚机制。 发布订阅&#xff1a;确保消息正确传递。 2、性…...

React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构

React 实战项目&#xff1a;微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇&#xff01;在前 29 篇文章中&#xff0c;我们从 React 的基础概念逐步深入到高级技巧&#xff0c;涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...

从实验室到产业:IndexTTS 在六大核心场景的落地实践

一、内容创作&#xff1a;重构数字内容生产范式 在短视频创作领域&#xff0c;IndexTTS 的语音克隆技术彻底改变了配音流程。B 站 UP 主通过 5 秒参考音频即可克隆出郭老师音色&#xff0c;生成的 “各位吴彦祖们大家好” 语音相似度达 97%&#xff0c;单条视频播放量突破百万…...

VSCode 使用CMake 构建 Qt 5 窗口程序

首先,目录结构如下图: 运行效果: cmake -B build cmake --build build 运行: windeployqt.exe F:\testQt5\build\Debug\app.exe main.cpp #include "mainwindow.h"#include <QAppli...