文章目录
- 1.什么是分布式锁- 2.分布式锁应该具备哪些条件- 3.分布式锁主流的实现方案- 4.未添加分布式锁存在的问题- - 4.1测试未添加分布式锁的代码- - 通过jmeter发送请求- 4.2 添加线程同步锁- - 集群部署- - 配置nginx- 修改jmeter端口号- 4.3 使用redis的setnx命令实现分布式锁- - 解决办法- 4.4 使用try、finally优化- 4.5 添加分布式锁的过期时间- 4.6 解决分布式锁命令的原子性问题- 4.7 把线程ID做为分布式锁的value- 5.使用redisson实现分布式锁- - 5.1 添加 Redisson 依赖- 5.2 获取 Redisson 客户端实例- 5.3 获取分布式锁- 5.4 Redisson分布式锁的卖现原理图- 6 Redisson相关锁介绍- - 6.1 可重入锁- 6.2 联锁概述- 6.3 RedLock
1.什么是分布式锁
问题描述: 随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的 JavaAPI 并不能提供分布式锁的能力,为了解决这个问题就需要一种跨IV的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
与分布式锁相对就的是单体结构中的锁 (单机锁) ,我们在写多线程程序时,避免同时操作一个共享变量产生数据问题,通常会使用一把锁来互斥以保证共享变量的正确性,其使用范围是在同一个JVM进程中。如果换做是不同机器上的MM进程,需要同时操作一个共享资源,如何互斥呢?现在的业务应用通常是微服务架构,这也意味着一个应用会部署多个进程,多个进程如果需要修改MySQL中的同一行记录,为了避免操作乱序导致脏数据,此时就需要引入分布式锁了。
2.分布式锁应该具备哪些条件
- 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
- 高可用的获取锁与释放锁
- 高性能的获取锁与释放锁
- 具备可重入特(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
- 具备锁失效机制,即自动解锁,防止死锁
- 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sYnTjYFm-1722400414013)(https://i-blog.csdnimg.cn/direct/b1a7a1d414134b9aa70439cdfcab343a.png#pic_center)]
3.分布式锁主流的实现方案
- 1.基于缓存(redis等)
- 2.基于zookeeper
每一种分布式锁解决方案都有各自的优缺点
- 1.性能:redis最高
- 2.可靠性:zookeeper最高
这里我们就基于redis实现分布式锁。
4.未添加分布式锁存在的问题
实现秒杀下单减库存案例:
创建springboot项目,导入redis依赖,在yml中进行redis的配置:
依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
配置:
spring:redis:
database:0
host:localhost
port:6379
4.1测试未添加分布式锁的代码
操作redis
# 在redis 中设置一个stock等于10
set stock 0
get stock
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dxgajzi1-1722400414016)(https://i-blog.csdnimg.cn/direct/ef0c473317374355ab5a87655c270118.png#pic_center)]
packagecn.js;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.annotation.Resource;@RestControllerpublicclassIndexcontroller{
@ResourceprivateStringRedisTemplate stringRedisTemplate;@RequestMapping("/deductstock")publicStringdeductstock(){
int stock =Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if(stock >0){
int realstock = stock -1;
stringRedisTemplate.opsForValue().set("stock", realstock +"");System.out.println("扣减成功,剩余库存:"+ realstock);}else{
System.out.println("扣减失败,库存不足");}return"end";}}
通过jmeter发送请求
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F5NVk3qU-1722400414016)(https://i-blog.csdnimg.cn/direct/3429f43f8f854fd195e66e1e40e9ae76.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hdTFecNu-1722400414017)(https://i-blog.csdnimg.cn/direct/c121e5c892e14a6681ce09be00b6c402.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9HYpALTw-1722400414017)(https://i-blog.csdnimg.cn/direct/22282de5812d4e29b1f8c43d1102ae8d.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wIKS2vCT-1722400414018)(https://i-blog.csdnimg.cn/direct/3b9acb34bae2425f94f9dde86d028117.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5BlOmXYR-1722400414019)(https://i-blog.csdnimg.cn/direct/e4b0d67689d6424db2ee1debe209e436.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eHVCF8OT-1722400414019)(https://i-blog.csdnimg.cn/direct/cae1990a429a488393effb3f0bfb3ba1.png#pic_center)]
通过命令再次查看redis
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcxYDh65-1722400414021)(https://i-blog.csdnimg.cn/direct/55c527fe429945c3b534b2a75259d1a7.png#pic_center)]
我们先清空redis再试一次
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFGbIALw-1722400414021)(https://i-blog.csdnimg.cn/direct/5147f817740d4572b8e6117133c092bc.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z1JSAMlg-1722400414022)(https://i-blog.csdnimg.cn/direct/f8c28a34e4644eb6afaa91ee8f375d05.png#pic_center)]
再次通过jmeter发送请求
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ghnRsbPi-1722400414023)(https://i-blog.csdnimg.cn/direct/389a3714469a484a9ad71557fe9a98e9.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JVMIYBVA-1722400414023)(https://i-blog.csdnimg.cn/direct/b66a747e9aca4e869dd94c5c3138d6bd.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9t14x1E-1722400414024)(https://i-blog.csdnimg.cn/direct/90d241b1fb7d4bed948ee5f5fccdd3f7.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b7lvc4UU-1722400414024)(https://i-blog.csdnimg.cn/direct/f4def8064b70485eab74a13dbfaf09f7.png#pic_center)]
两次结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JkP4qbls-1722400414025)(https://i-blog.csdnimg.cn/direct/cce8674899264981a02de741c1b9b6ee.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Scub7Otw-1722400414025)(https://i-blog.csdnimg.cn/direct/31b5243e1308427a9ad5827e2dd746d3.png#pic_center)]
研究代码发现存在线程安全问题,接下来可以加个同步代码锁synchronized进行优化
4.2 添加线程同步锁
packagecn.js;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.annotation.Resource;@RestControllerpublicclassIndexcontroller{
@ResourceprivateStringRedisTemplate stringRedisTemplate;@RequestMapping("/deductstock")publicStringdeductstock(){
synchronized(this){
int stock =Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if(stock >0){
int realstock = stock -1;
stringRedisTemplate.opsForValue().set("stock", realstock +"");System.out.println("扣减成功,剩余库存:"+ realstock);}else{
System.out.println("扣减失败,库存不足");}}return"end";}}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ryOIcER2-1722400414026)(https://i-blog.csdnimg.cn/direct/1c6cf8df169d46efa7407b83576df1a5.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3rdlaJos-1722400414026)(https://i-blog.csdnimg.cn/direct/21e9dd73363048a594cacbd4100a837a.png#pic_center)]
目前我们的环境是单体服务,可以通过 synchronized JVM锁进行解决,但是如果是集群部署的话,synchronized锁是没有用的
集群部署
配置nginx
打开nginx中的nginx.conf文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Idv02YWX-1722400414027)(https://i-blog.csdnimg.cn/direct/d8c2882fd7464544964532c5802a8cbd.png#pic_center)]
在.idea的workspace.xml中配置
<optionname="configurationTypes"><set><optionvalue="springBootApplicationconfigurationType"/></set></option>
修改jmeter端口号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-80Aims7W-1722400414027)(https://i-blog.csdnimg.cn/direct/b435e4a76504444f890db6d169514e1c.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jFMG2ZdQ-1722400414028)(https://i-blog.csdnimg.cn/direct/ee238cfb50cd4623ba4d774f604fe665.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hEjuSheC-1722400414028)(https://i-blog.csdnimg.cn/direct/11782201a0774907b7c80918d0c43ae4.png#pic_center)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ql0rntWa-1722400414029)(https://i-blog.csdnimg.cn/direct/180409fe2da6446ca9628af0483fcd6d.png#pic_center)]
可以看到集群部署会出现超卖问题
重启IDEA
最后使用imeter工具,模拟高并发场景进行压力测试,查看控制台打印日志,发现还是出现库存量一样的数据,说明超卖问题还是存在。
分析得出:在分布式环境下synchronized是不起作用的,因为一个synchronized只在一个tomcat的iym进程内有效,在一个分布式系统,如何解决并发的资源争抢问题呢?
4.3 使用redis的setnx命令实现分布式锁
解决办法
可以通过redis的setnx命令的互斥性:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S17k8hea-1722400414029)(https://i-blog.csdnimg.cn/direct/5f8df106ef884ba9ae9f4331ca63564b.png#pic_center)]
packagecn.js;importorg.springframework
版权归原作者 北执南念 所有, 如有侵权,请联系我们删除。