微服务项目【分布式锁】
创建Redisson模块
第1步:基于Spring Initialzr方式创建zmall-redisson模块
第2步:在zmall-redisson模块中添加相关依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!--redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><!--commons-pool2-->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency><!--redisson-->
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.17.0</version>
</dependency>
第3步:配置application.yml
server:port: 8081
spring:redis:host: 127.0.0.1password: 123456database: 0port: 6379
模拟高并发场景秒杀下单
场景模拟
@RestController
public class RedissonController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping("/updateStock")public String updateStock() {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");//jedis.set("stock",realStock+"");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}return "end";}
}
案例演示
- 示例一:单线程情况
直接打开浏览器输入:http://localhost:8081/updateStock,查看redis中库存扣减情况。
- 示例二:多线程情况
第1步:配置多启动服务
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IJol4PhL-1676642300167)(images\20220824220250.jpg)]](https://img-blog.csdnimg.cn/68de7e690af54100bf447281b604feb9.jpeg)
第2步:配置nginx,实现负载均衡
upstream tomcats{server 127.0.0.1:8081 weight=1;server 127.0.0.1:8082 weight=2;
}server
{listen 80;server_name localhost;location / {proxy_pass http://tomcats/;}
}
第3步:配置jmeter,实现压测
创建测试用例,循环发送4组线程,每组200个;
查看redis中库存结果为0;查看多服务控制台信息均显示扣减失败,库存不足提示。
- 结果分析
1)在单线程情况下,调用updatestock方法扣减库存,订单下单正常(没啥好说的)
2)在多线程情况下,调用updatestock方法扣减库存正常,订单下单异常(超卖了)原因分析:在高并发情况下同时多个线程调用updateStock方法,按照正常思路线程1、线程2、线程3应该是分别实现库存减一(在库存为100的情况下,现在应该剩余97),同时生成三个秒杀订单;然后并发情况下根本不会按照剧本设计来执行,而是出现了线程1、线程2、线程3同时扣减库存,导致库存剩余99,但是订单却产生了3个,说明超卖了。
JVM级锁与redis级分布式锁
JVM级锁
@RestController
public class RedissonController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping("/updateStock")public String updateStock() {//jvm级锁,单机锁synchronized (this){int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");//jedis.set("stock",realStock+"");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}}return "end";}
}
jvm级的同步锁,单机锁。上述同步代码块中,在单机环境下同一时刻只有一个线程能进行秒杀下单库存扣减,完毕之后才能有后续线程进入。但是在分布式环境下依然还是会出现商品超卖情况。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OCvM5BJf-1676642300170)(images\2022-08-24_144236.png)]](https://img-blog.csdnimg.cn/962ebd33d65f4867b20ba1ad0f03e40f.png)
重新启动jmeter压测,连续发送4组,每组200个请求。
redis级分布式锁
什么是setnx
格式:setnx key value
将key的值设置为value,当且仅当key不存在;若给定的key存在,则setnx不做任何动作。
setnx是set if not exists(如果不存在,则set)的简写。
setnx "zking" "xiaoliu" 第一次设置有效
setnx "zking" "xiaoliu666" 第二次设置无效
第一次使用setnx设置zking直接成功,第二次使用setnx设置zking则失败,也意味着加锁失败。
redis级分布式锁之setnx使用
@RestController
public class RedissonController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping("/updateStock")public String updateStock() {//使用redis级分布式锁setnx加锁String lockKey="lockKey";Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"zking");if(!flag)return "error_code";int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");//jedis.set("stock",realStock+"");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}//解锁stringRedisTemplate.delete(lockKey);}return "end";
}
场景分析
基于以上redis分布式锁setnx的代码,实现场景分析。
- 问题1:执行扣减库存业务时出现异常,导致无法正常删除锁,从而形成死锁。
解决办法:通过try/catch/finally代码块解决。
//使用redis级分布式锁setnx加锁
String lockKey="lockKey";
try{Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"zking");if(!flag)return "error_code";int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");//jedis.set("stock",realStock+"");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}
}finally{//解锁stringRedisTemplate.delete(lockKey);
}
- 问题2:执行扣减库存业务是如果Redis服务宕机,基于上述问题1的finally块就无意义了,还是死锁。
解决办法:加锁时设置过期时间,确保原子性。
//使用redis级分布式锁setnx加锁
String lockKey="lockKey";
try{Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"zking",10,TimeUnit.SECONDS);if(!flag)return "error_code";int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");//jedis.set("stock",realStock+"");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}
}finally{//解锁stringRedisTemplate.delete(lockKey);
}
- 问题3:高并发场景下,线程执行先后顺序无法把控(自己加的锁被其他线程释放掉了
场景分析:
线程1:业务执行时间15s,加锁时间10s,那么导致业务未执行完成锁被提前释放;
线程2:业务执行时间8s,加锁时间10s;
线程3:业务执行时间5s,加锁时间10s,那么导致线程2的任务还没有执行完成就是线程3将所删除掉了;以此类推,只要是高并发场景一直存在,那么锁一直处于失效状态(永久失效)
解决办法:可以在加锁的时候设置一个线程ID,只有是相同的线程ID才能进行解锁操作。
//使用redis级分布式锁setnx加锁
String lockKey="lockKey";
String clientId= UUID.randomUUID().toString();
try{Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,clientId,10,TimeUnit.SECONDS);if(!flag)return "error_code";int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");//jedis.set("stock",realStock+"");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}
}finally{//只有是相同的线程ID时才进行解锁操作if(stringRedisTemplate.opsForValue().get(lockKey).equals(clientId)) {//业务代码执行完毕删除redis锁(解锁)stringRedisTemplate.delete(lockKey);}
}
**问题4:**锁要加多次时间才是最合理有效的?
解决办法:redisson,看门狗机制。
redisson分布式锁+源码解读
什么是Redisson
Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重对 Redis 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发。
特点:
- 互斥:在分布式高并发的条件下,我们最需要保证,同一时刻只能有一个线程获得锁,这是最基本的一点。
- 防止死锁:在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来得及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。所以分布式非常有必要设置锁的有效时间,确保系统出现故障后,在一定时间内能够主动去释放锁,避免造成死锁的情况。
- 性能:对于访问量大的共享资源,需要考虑减少锁等待的时间,避免导致大量线程阻塞。所以在锁的设计时,需要考虑两点。
- 锁的颗粒度要尽量小。比如你要通过锁来减库存,那这个锁的名称你可以设置成是商品的ID,而不是任取名称。这样这个锁只对当前商品有效,锁的颗粒度小。
- 锁的范围尽量要小。比如只要锁2行代码就可以解决问题的,那就不要去锁10行代码了。
- 重入:ReentrantLock是可重入锁,那它的特点就是:同一个线程可以重复拿到同一个资源的锁。重入锁非常有利于资源的高效利用。
Redisson工作原理
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XW61ORr2-1676642300172)(images\1090617-20190618183025891-1248337684.jpg)]](https://img-blog.csdnimg.cn/d664f01dc81c451b9e92659ac3d12049.jpeg)
入门案例
创建RedissonConfig配置类
@Configuration
public class RedissonConfig {@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private String port;@Value("${spring.redis.password}")private String password;@Beanpublic RedissonClient redissonClient(){Config config=new Config();String url="redis://"+host+":"+port;config.useSingleServer().setAddress(url).setPassword(password).setDatabase(0);return Redisson.create(config);}
}
使用redisson分布式锁实现秒杀下单
@RequestMapping("/updateStock")
public String updateStock() {String lockKey="lockKey";RLock clientLock = redissonClient.getLock(lockKey);clientLock.lock();try {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");//jedis.set("stock",realStock+"");System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}} finally {//解锁clientLock.unlock();}return "end";
}
重新启动jmeter压测,连续发送4组,每组200个请求。查看多服务控制台,结果显示秒杀订单下单正常,无超卖情况发生。
秒杀项目整合redisson实现分布式锁
第1步:在zmall-order模块中配置pom.xml
<!--redisson-->
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.17.0</version>
</dependency>
第2步:创建Redisson配置类
@Configuration
public class RedissonConfig {@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private String port;@Value("${spring.redis.password}")private String password;@Beanpublic RedissonClient redissonClient(){Config config=new Config();String url="redis://"+host+":"+port;config.useSingleServer().setAddress(url).setPassword(password).setDatabase(0);return Redisson.create(config);}
}
第3步:整合项目实现Redisson分布式锁
@Transactional
@Override
public JsonResponseBody<?> createKillOrder(User user, Integer pid) {//6.根据秒杀商品ID和用户ID判断是否重复抢购Order order = redisService.getKillOrderByUidAndPid(user.getId(), pid);if(null!=order)return new JsonResponseBody<>(JsonResponseStatus.ORDER_REPART);RLock clientLock = redissonClient.getLock("scekill:goods:" + pid);clientLock.lock();try {//7.Redis库存预减long stock = redisService.decrement(pid);if (stock < 0) {redisService.increment(pid);return new JsonResponseBody<>(JsonResponseStatus.STOCK_EMPTY);}//创建订单order = new Order();order.setUserId(user.getId());order.setLoginName(user.getLoginName());order.setPid(pid);//将生成的秒杀订单保存到Redis中redisService.setKillOrderToRedis(pid, order);//将生成的秒杀订单推送到RabbitMQ中的订单队列中rabbitTemplate.convertAndSend(RabbitmqOrderConfig.ORDER_EXCHANGE,RabbitmqOrderConfig.ORDER_ROUTING_KEY, order);}catch (Exception e){e.printStackTrace();throw new BusinessException(JsonResponseStatus.ORDER_ERROR);}finally {clientLock.unlock();}return new JsonResponseBody<>();
}
重新启动jmeter压测。
相关文章:
微服务项目【分布式锁】
创建Redisson模块 第1步:基于Spring Initialzr方式创建zmall-redisson模块 第2步:在zmall-redisson模块中添加相关依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</a…...
JavaWeb5-线程常用属性
目录 1.ID 2.名称 3.状态 4.优先级 5.是否守护线程 5.1.线程类型: ①用户线程(main线程默认是用户线程) ②守护线程(后台/系统线程) 5.2.守护线程作用 5.3.守护线程应用 5.4.守护线程使用 ①在用户线程&am…...
JVM调优及垃圾回收GC
一、说一说JVM的内存模型。JVM的运行时内存也叫做JVM堆,从GC的角度可以将JVM分为新生代、老年代和永久代。其中新生代默认占1/3堆内存空间,老年代默认占2/3堆内存空间,永久代占非常少的对内存空间。新生代又分为Eden区、SurvivorFrom区和Surv…...
JAVA练习53-打乱数组
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、题目-打乱数组 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 总结 前言 提示:这里可以添加本文要记录的大概内容: 2月17日练习内…...
基于RK3588的嵌入式linux系统开发(三)——Uboot镜像文件合成
本章uboot镜像文件的合成包括官网必备文件rkbin下载和uboot镜像文件合成两部分内容,具体分别如下所述。 (一)下载rkbin文件包 以上uboot编译生成的uboot镜像不能直接烧录到板卡中运行,需要与atf、bl31、ddr配置文件等必备文件合成…...
wireshark抓包后通过工具分包
分包说明:关于现场问题分析,一般都是通过日志,这个属于程序中加的打印,或存数据库,或者存文本形式,这种一般比较符合程序逻辑;还有一种就是涉及到网络通信方面的,需要通过抓包来分析…...
举个栗子~Tableau 技巧(251):统一多个工作表的坐标轴范围
在工作汇报场景,有一个很常见、很多数据粉反馈的需求:同一看板上的两个图表,因为轴范围不一致(如下图),很难直观比较。有什么办法可以统一它们的坐标轴范围呢? 类似需求,不论两个还是…...
Centos7 调整磁盘空间
1. 查看磁盘空间占用情况: df -h 可以看到 /home 有很多剩余空间,占了绝大部分, 而我又很少把文件放在home下。 2. 备份 /home 下的内容: cp -r /home/ /homebak/ 3. 关闭home进程: fuser -m -v -i -k /home 报错: -bash: fuser…...
小菜版考试系统——“C”
各位CSDN的uu们你们好呀,今天,小雅兰的内容是小菜版考试系统,最近一直在忙C语言课程设计的事,那么,就请uu们看看我的学习成果吧。 课程设计任务 摘要 题目分析 流程图 关键程序代码 程序运行结果 结论与心得 参…...
Twitter被封号了?最详细的申诉教程在此
由于Twitter检测系统是十分敏感的,所以在运营的时候很容易莫名就出现“此账号被封禁”或者“此账号被冻结”的情况。出现这种情况大多是因为账号发送了垃圾信息、面临安全风险、发太多广告或者太久没上线被判为机器人这几个原因。被封号后,我们可以通过向…...
Docker 安装配置
本章背景知识 本章主要介绍在 Centos 操作系统平台上进行安装和配置Docker Engine。 环境准备 1、操作系统支持。 CentOS、Debian、Fedora、Raspbian、RHEL、SLES、Ubuntu、Binaries 2、启用yum 软件仓库源。 centos-extras 编者注:Centos 默认已经开启cento…...
死锁检测组件-设想
死锁检测组件-设想 现在有三个临界资源和三把锁绑定了,三把锁又分别被三个线程占用。(不用关注临界资源,因为锁和临界资源是绑定的) 但现在出现这种情况:线程1去申请获取锁2,线程2申请获取锁3,…...
线程池的使用
为什么要使用线程池 复习一下创建线程的几种方式: 继承Thread 实现Runnable 实现Callable 但是如果频繁的创建/销毁线程,就会造成资源浪费。这时候就需要将线程创建好之后存起来,以后要用取出来,用完后再放回去。 注意 ÿ…...
字节码指令
目录 2.1 入门 2.2 javap 工具 2.3 图解方法执行流程 1)原始 java 代码 2)编译后的字节码文件 3)常量池载入运行时常量池 4)方法字节码载入方法区 5)main 线程开始运行,分配栈帧内存 6)…...
TLS/SSL证书彻底扫盲
证书格式 pem Privacy Enhanced Mail文本格式,以 -----BEGIN CERTIFICATE----- 开头,以-----END CERTIFICATE-----结尾 der 二进制格式,只保存证书,不保存私钥java和window服务器常见 pfx/p12 Predecessor of PKCS#12二进制格式&…...
WGCNA | 值得你深入学习的生信分析方法!~(网状分析-第五步-高级可视化)
1写在前面 前面我们用WGCNA分析完成了一系列的分析,聚类分割模块。🥰 随后进一步筛选,找到与我们感兴趣的表型或者临床特征相关的模块,而且进行了模块内部分析。😘 再然后是对感兴趣模块进行功能注释,了解模…...
try catch finally执行顺序
try catch finally,try里有return,finally还执行么?答案: 执行,并且返回return时,finally的执行早于try。try-catch-finally的执行顺序无return当try中的t()没有抛出异常public static void main(String[] …...
2023年数学建模美赛D题(Prioritizing the UN Sustainability Goals)分析与编程
2023年数学建模美赛D题分析建模与编程 重要说明: 本文介绍2023年美赛题目,并进行简单分析;本文首先对 D题进行深入分析,其它题目分析详见专题讨论;本文及专题分析将在 2月17日每3小时更新一次,完全免费&am…...
35岁测试工程师被辞退,给你们一个忠告
一:前言:人生的十字路口静坐反思 入软件测试这一行至今已经10年多,承蒙领导们的照顾与重用,同事的支持与信任,我的职业发展算是相对较好,从入行到各类测试技术岗位,再到测试总监,再转…...
华为OD机试题 - 租车骑绿岛(JavaScript)
最近更新的博客 2023新华为OD机试题 - 斗地主(JavaScript)2023新华为OD机试题 - 箱子之形摆放(JavaScript)2023新华为OD机试题 - 考古学家(JavaScript)2023新华为OD机试题 - 相同数字的积木游戏 1(JavaScript)2023新华为OD机试题 - 最多等和不相交连续子序列(JavaScri…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合
作者:来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布,Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明,Elastic 作为 …...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
CSS3相关知识点
CSS3相关知识点 CSS3私有前缀私有前缀私有前缀存在的意义常见浏览器的私有前缀 CSS3基本语法CSS3 新增长度单位CSS3 新增颜色设置方式CSS3 新增选择器CSS3 新增盒模型相关属性box-sizing 怪异盒模型resize调整盒子大小box-shadow 盒子阴影opacity 不透明度 CSS3 新增背景属性ba…...
文件上传漏洞防御全攻略
要全面防范文件上传漏洞,需构建多层防御体系,结合技术验证、存储隔离与权限控制: 🔒 一、基础防护层 前端校验(仅辅助) 通过JavaScript限制文件后缀名(白名单)和大小,提…...
