微服务项目【分布式锁】
创建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步:配置多启动服务
第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级的同步锁,单机锁。上述同步代码块中,在单机环境下同一时刻只有一个线程能进行秒杀下单库存扣减,完毕之后才能有后续线程进入。但是在分布式环境下依然还是会出现商品超卖情况。
重新启动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工作原理
入门案例
创建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…...
Linux下Python脚本的编写解析fio(minimal格式)(三)
在服务器测试(storage)过程中,会看到很多人写跑fio的脚本用minimal格式来解析,因为这种格式返回的结果对与脚本(shell,python)解析log非常方便.下面介绍一下这种方式下,用Python来解析log 1 一般客户会要求结果中出现一下参数的值: bandwidth…...
【实战场景二】如何设计一个分布式锁?
如何优雅的设计一个分布式锁?如何设计一个分布式锁?1、什么是分布式锁2、那么分布式锁,具备什么条件呢?3、设计分布式锁有哪些方式?3.1 利用redis实现分布式锁原理3.2 基于数据库做分布式锁3.3 基于zookeeper实现分布式…...
Java中ThreadLocal类详解
ThreadLocal从名字上我们看出,它叫做本地线程变量,每个线程都有各自的的变量,而不再是我们之前的两个线程共用同一个变量;以这个类创建的变量,在多个线程都用到这个变量时,可以为每一个线程创建一个变量副本…...
从一致性角度考虑推荐冷启动长尾推荐问题(一)
前言:目前中长尾推荐的方法有很多,主流的方法有几类比如:1)在没有项目ID嵌入的情况下提高推荐模型的鲁棒性,2)利用有限的交互数据提高学习效率,如使用元学习方法;3)利用物品侧面信息,便于物品ID嵌入的初始化࿰…...
电脑(Windows)常用快捷键
简述:实用的键盘快捷键是一个程序员的必备技能,下面给大家整理了一下常用的键盘快捷键; ⭐CtrlP 打开“打印机”对话框; ⭐CtrlW 关闭当前网页; ⭐CtrlF 查找(网页内查找); ⭐…...
Java类加载器
1 类加载器 1.1 类加载 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化。如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为…...
信号完整性设计规则之单根信号失真最小化
本文内容从《信号完整性与电源完整性分析》整理而来,加入了自己的理解,如有错误,欢迎批评指正。 1. 通常采用所能容许的最长上升边。 上升边越短,带宽越大,信号完整性问题越严重。 2. 使用可控阻抗走线。 可控阻抗…...
Python3 数据结构
列表 Python中列表是可变的,这是它区别于字符串和元组的最重要的特点,一句话概括即:列表可以修改,而字符串和元组不能。 以下是 Python 中列表的方法: 方法 描述 list.append(x) 把一个元素添加到列表的结尾…...
Compose-Navigation带参传递
带参传递 目前 compose 还不支持传入对象作为参数! 简单双参数 根目录下新建文件夹 entity,新建单例类 ContentType 作为数据类存储位置 新增数据类 DemoContent,这表示我们需要传入的两个参数,后面带问号判空 object ContentT…...
【函数栈帧的创建和销毁】 -- 神仙级别底层原理,你学会了吗?
文章目录1.函数的调用方式 2.函数在栈区上的动作 1.函数的调用方式 相信你对调用函数一点都不陌生,但是在调用函数的过程中,却存在着很多你无法见到的东西,这是底层信息,想要理解透彻,就得深入底层去观察。 本文以…...
哈尔滨做网站seo的/西安企业seo外包服务公司
故事是这样发生的:前几天和同事在公司吃饭,同事讲起他早年间相亲的故事。女方是某IT公司的HR,研究生学历,两人第一次见面的一段对话,觉得非常的有意思。 两人是共同的朋友介绍的,第一次见面地点商定在一家餐…...
公司网站在哪里做/十大广告公司排名
author:咔咔 wechat:fangkangfk function xmltoarr($path){//xml字符串转数组$xml $path;//XML文件$objectxml simplexml_load_string($xml);//将文件转换成 对象$xmljson json_encode($objectxml );//将对象转换个JSON$xmlarrayjson_decode($xmljson,…...
商水建设局网站/网站开发培训
【摘要】为帮助广大考生备考,网校特整理职称计算机Excel考点分类汇总表的建立和删除的辅导资料,祝您在环球网校学习愉快!(l)分类汇总利用Excel可以按数据清单中某个字段的值进行分类,并且按这些不同的类进行汇总。分类 汇总前必须先对数据按要分类的字段进行排序。操作步骤:1)首…...
国外设计案例网站/查关键词排名工具app
原因 因为安装了vim插件 在setting里找到vim插件,取消勾选即可解决 pycharm会提示重启,重启之后ctrlc一系列的快捷键就可以正常使用了...
怎么做中英文双语网站/网络的推广
如果想了解更多的知识,可以去我的机器学习之路 The Road To Machine Learning通道 第3章 k近邻法 1.kkk近邻法是基本且简单的分类与回归方法。kkk近邻法的基本做法是:对给定的训练实例点和输入实例点,首先确定输入实例点的kkk个…...
17网站一起做/云南网络推广seo代理公司
转自: https://blog.csdn.net/y511374875/article/details/77845240 三种方式手动重启Arduino 1.Arduino板上重新编写代码时,Arduino将重新设置 2.Arduino软件中打开串行终端,同时将Arduino板连接到计算机。打开串行终端时,Arduin…...