【Redis】集群环境下的并发问题

通过加锁可以解决在单机情况下的一人一单安全问题,但是在集群模式下就不行了。

一、搭建集群

1、我们将服务启动两份,端口分别为8081和8082,从而模拟集群效果:

首先,复制原来的user-service启动配置,或者 ctrl + D

作用就是将新启动配置拷贝了一份,每创建一次新的启动配置,将来IDEA底层就会给我们创建两台tomcat,分别启动,就可以模拟两台机器了

image-20240528074136124 image-20240528074251969

然后,在弹出的窗口中,填写信息,由于将一个服务启动两次会有端口冲突,所以这里需要配置一个 -Dserver.prot 去避免端口冲突。

-D代表参数,server.port就是我们在yml文件中配的方式,这里配置的端口就是用来覆盖yml文件里的端口的。

image-20240528074407829

最后重启两个服务

image-20240528074631753

现在我们就形成了一个集群了,它有两个节点的集群


二、负载均衡

2、然后修改nginx的conf目录下的nginx.conf文件,配置反向代理和负载均衡:

实现:当你访问Nginx的那一刻,它就会反向代理到8081/8082两个节点上,默认采用的就是轮询的负载均衡规则,这样它们两个都能访问到了。

image-20240528074848217

然后通过 nginx.exe -s reload 重新加载Nginx的配置文件

image-20240528075350191

访问 http://localhost:8080/api/voucher/list/1,8080端口不是8081,也不是8082,而现在访问的8080就是Nginx

image-20240528075535730

Nginx监听到8080后,它的api路径就会代理到backed,backed就会负载均衡到8081和8082

image-20240528075731480

所以我们访问8080其实就是在访问8081和8082节点。多访问几次 http://localhost:8080/api/voucher/list/1,可以发现8081和8082都有请求,说明这两个节点都被访问到了,也就是说它们其实有了这样一个负载均衡的效果了

image-20240528080126609


三、测试秒杀下单

在锁的地方打断点,并且将id为10的优惠券库存改为100,并且清除订单表中所有订单

利用Postman使用同一个用户的token发送请求

image-20240528082352307

这里明明有锁,并且用户ID是一样的,就表示锁没锁住。两台服务都进到断点了就有问题了

image-20240528082604751

如果此时放行,它们此时做查询,查到的结果都是count为0,如果此时放行,它俩都会去减库存,然后都创建订单。

image-20240528082824204

此时去数据库看,可以发现库存扣了2个,并且有两个订单。

image-20240528083315811

这就说明我们又一次出现了并发的安全问题。也就是在集群下,虽然我们使用了 synchronized锁,但是并没有锁住。


四、有关锁失效原因分析

正常串行执行情况应该如下图

image-20240528083500007

现在在多线程并发执行的情况下,它不可能每次都这么正常的串行执行,它可能会出现交叉执行的情况,一旦出现交叉执行,这个订单就会被插入两次,如下图

image-20240528083732355

后来我们加了锁解决了这个问题,也就是说一个线程来了后必选先获取锁,拿到锁后才可以去执行查询订单的动作。

此时就锁另外一个线程要来获取锁,但由于线程1已经拿到了锁,因此线程2获取锁会失败。

根据synchronized的原理,它会等待,等待锁释放,然后就可以获取锁成功了,但此时再去执行查询,由于线程1已经插入了订单了,因此再来查已经存在了,此时订单就会报错。

image-20240528085514865

现在我们不是一台这样,而是多台,大家知道,在一个JVM的内部,锁的原理是:在JVM的内部维护了一个锁的监视器对象,这个监视器对象我们用的是UserId,Id在常量池中。在这一个JVM的内部维护了一个池子,在Id相同的情况下,就永远是同一个锁,也就是锁的监视器是同一个。

因此无论是线程1也好,还是线程2也好,当线程1来获取锁的时候,锁监视器就会记录锁的名称,当形参2再来获取的时候,它一看这已经有了,它就不能获取了。

image-20240528085911831

但是当我们做集群部署的时候,一个新的部署,就相当于是一个全新的tomcat,也就意味着是一个全新的JVM,也就是说有两套JVM。

有两个JVM就有各自的堆、栈、方法区等,因此JVM2也会有自己的常量池,它在监视锁的时候,就会有一个全新的锁监视器了,跟JVM1的锁监视器不是同一个。

那么假设在服务器A的tomcat内部,有两个线程,这两个线程由于使用的是同一份代码,那么他们的锁对象是同一个,是可以实现互斥的,但是如果现在是服务器B的tomcat内部,又有两个线程,但是他们的锁对象写的虽然和服务器A一样,但是锁对象却不是同一个,所以线程3和线程4可以实现互斥,但是却无法和线程1和线程2实现互斥,这就是 集群环境下,syn锁失效的原因,在这种情况下,我们就需要使用分布式锁来解决这个问题。

image-20240528090241935

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值