前言:
在Java后端业务中, 如果我们开启了均衡负载模式,也就是多台服务器处理前端的请求,就会产生一个问题:多台服务器就会有多个JVM,多个JVM就会导致服务器集群下的并发问题。我们在这里提出的解决思路是把锁交给Redis来实现,因为Redis是单线程的。而最基础的Redis解决集群模式下的并发问题的核心解决方案是使用Setnx构造分布式锁,下文来让我们详细的看一下过程。

目录
核心思路:
其实整个爆改过程的思路都很清楚,我们先来解释一下SETNX的作用:
SETNX key valueSETNX命令的作用是:只有当指定的键名
key不存在时,将键值对存储到Redis数据库中。如果键名key已经存在,则不执行任何操作。
那么整体的核心思路就是:让当前线程尝试先创建A再执行业务逻辑代码,如果A不存在,就进行创建,并执行相关业务逻辑,业务逻辑执行完毕后释放A;如果A存在,那么说明此时有其他的线程在执行业务逻辑代码,则拒绝当前线程执行业务逻辑(挂起线程)
其实就是通过SETNX构造了一个唯一数据,并且把这个数据作为锁。这种思路使得我们的锁不再局限于某一个JAVA对象,从而避开了synchronized只能在JVM内部生效。解决了集群架构下多JVM上锁困难的困境

具体业务逻辑:
本次的具体业务应用场景是优惠卷秒杀场景,简单的来讲:就是商家发放优惠卷,用户进行抢购。而在优惠卷秒杀业务中,我们需要注意的是一人一单问题。一人一单就是一个用户只允许下一单。而我们本项目的背景是允许多端登录。我们可以想一想这个问题的核心问题:如果多端登录,在服务器集群架构的模式下,如果我们还是传统模式加锁,就会出现这个问题:
用户A同时登录的电脑和手机,在以前的模式下:我们是简单粗暴的给一人一单核心代码直接解锁。但这样做有两个问题:
1.如果直接加锁,那么也就是说程序的并发性大大降低,我们一次只能处理一个用户的优惠卷订单,效率大大降低。
2.如果是在集群模式下,传统的锁只能在一个JVM内生效,并不能跨JVM。如果用户的电脑购买优惠卷请求进入到了服务器A,而用户的手机购买优惠卷请求进入到了服务器B,那么就有可能造成优惠卷超卖的情况。
总结一下优惠卷超卖场景的业务逻辑
- 查询优惠卷是否存在
- 查询优惠卷是否在售卖时间
- 查询当前优惠卷是否还有库存
- 查询用户是否已经下过单(如果有直接返回给前端Result,封装消息类)
- 扣减优惠卷库存
- 创建订单ID
- 返回订单号给前端
- 封装订单相关信息,更新数据库
在这几步中,从4-8步就是一人一单问题,而解决优惠卷秒杀问题,大部分情况就是在解决这个问题。
业务问题解决思路
我们来一步一步看当前有哪些问题需要我们解决:
1.选择加锁问题:
在我们最开始的加锁中,我们选择的是synchronized关键字,但是它会导致程序的并发性大大降低。并且无法跨JVM容器生效。
我们为了解决synchronized关键字无法跨JVM容器生效,采用了SETNX关键字。通过这种方法,我们解决了锁跨JVM容器生效。
synchronized 是基于JVM层面的同步机制,它会锁定整个方法,而且它的作用范围限定在单个JVM内。在分布式系统或者集群环境中,synchronized 不能跨JVM工作,因此不适合作为分布式锁使用。而分布式锁 simpleRedisLock 是基于Redis实现的,可以跨多个应用实例工作,适用于分布式系统。
但是它本质上和synchronized关键字的作用一样,并没有解决程序的并发性大大降低的问题。只不过以前我们是通过synchronized关键字拦截线程,现在是通过SETNX拦截线程。
那么让我们来逆推一下思路,加锁是为了解决两个问题:
- 同一用户在不同端多次购买的相同优惠卷的行为
- 不同用户同时购买同一优惠

文章介绍了在Java后端多服务器环境中,如何使用Redis的SETNX实现分布式锁来解决并发问题,重点讨论了优惠券秒杀场景中的并发控制策略,以及如何通过lua脚本确保锁的删除操作原子性,同时提到了SpringMVC事务失效的问题及其解决方案。
最低0.47元/天 解锁文章
2738





