问题描述: 随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的 JavaAPI 并不能提供分布式锁的能力,为了解决这个问题就需要一种跨IV的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
与分布式锁相对就的是单体结构中的锁 (单机锁) ,我们在写多线程程序时,避免同时操作一个共享变量产生数据问题,通常会使用一把锁来互斥以保证共享变量的正确性,其使用范围是在同一个JVM进程中。如果换做是不同机器上的MM进程,需要同时操作一个共享资源,如何互斥呢?现在的业务应用通常是微服务架构,这也意味着一个应用会部署多个进程,多个进程如果需要修改MySQL中的同一行记录,为了避免操作乱序导致脏数据,此时就需要引入分布式锁了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sYnTjYFm-1722400414013)(https://i-blog.csdnimg.cn/direct/b1a7a1d414134b9aa70439cdfcab343a.png#pic_center)]
每一种分布式锁解决方案都有各自的优缺点
这里我们就基于redis实现分布式锁。
实现秒杀下单减库存案例:
创建springboot项目,导入redis依赖,在yml中进行redis的配置:
依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
配置:
spring:
redis :
database:0
host:localhost
port:6379
操作redis
# 在redis 中设置一个stock等于10
set stock 0
get stock
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dxgajzi1-1722400414016)(https://i-blog.csdnimg.cn/direct/ef0c473317374355ab5a87655c270118.png#pic_center)]
package cn.js;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class Indexcontroller {
@Resource
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deductstock")
public String deductstock() {
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-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进行优化
package cn.js;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class Indexcontroller {
@Resource
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deductstock")
public String deductstock() {
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.conf文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Idv02YWX-1722400414027)(https://i-blog.csdnimg.cn/direct/d8c2882fd7464544964532c5802a8cbd.png#pic_center)]
在.idea的workspace.xml中配置
<option name="configurationTypes">
<set>
<option value="springBootApplicationconfigurationType"/>
set>
option>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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进程内有效,在一个分布式系统,如何解决并发的资源争抢问题呢?
可以通过redis的setnx命令的互斥性:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S17k8hea-1722400414029)(https://i-blog.csdnimg.cn/direct/5f8df106ef884ba9ae9f4331ca63564b.png#pic_center)]
package cn.js;
import org.springframework