本章演示在springboot项目中的高并发demo,演示导致的问题,以及单机部署下的解决方案和集群部署下的解决方式以及分布式下的解决方案。
一、单机模式下高并发问题
前提:先写一个减扣数据库产品数量的一个接口作为测试。
拿以前springboot整合布隆过滤网篇的一个接口直接做改造:假设编号为2的苹果库存还有一个,现在有个接口去买这个苹果并生成订单号以便于后期支付,得到如下:
1、数据表:
2、接口
通过jmeter模拟一秒钟有100个用户购买这个苹果,结果会是什么?
会发现直接卖爆了,一个苹果被卖了几十单。怎么解决这个并发问题呢?
PS:java提供了锁来处理
1、乐观锁
CAS先比较再交换,Java中提供了Atomic开头的类,例如AtomicInteger、AtomicLong、AtomicReference等原子类都是此思想来支持CAS操作的。进行如下改造,来实现先比较在修改值的方式解决该问题。其实就是在把cas想做是一个原子操作。改造方式就是例如给商品表增加一个字段用来表示该次原子性操作时,他应该是什么值,若是则修改,不然就不修改。如下:
增加一个number字段,原理就是每次修改时带上这个number条件,而每次减少count后修改number的值(原子性)第一个请求的用户这样处理,其他同时查到这个订单的其他用户,在减少count时根据number条件却查不到这个订单了从而无法再生成订单。代码如下:
继续jmeter测试,再看看结果如何?
发现通过这种方式的确实现了防止超卖的现象。
- 优点:不用加锁,不会阻塞其他线程,性能相比较好。
- 缺点:需要增加表字段,并且由于是在数据库层面保持原子性可能导致多事务操作操作同一数据时导致冲突,引起数据一致性问题。
结论:所以在并发较少的情况下可以使用乐观锁方式。
2、悲观锁
将通过下面两种锁来进行演示。
2.1、synchronized锁
改造代码如下:
通过测试得出:
发现实现了防止超卖,但是synchronized锁是基于jvm层面的,因此并不适用于集群模式。集群模式会涉及到一个服务的多实例,就会有多个jvm,synchronized只能保证当前实例在当前jvm下的原子性操作。
我们用idea模拟一个集群来进行测试,如下:
执行一下jmeter,看看结果是什么?
我们可以看到模拟的每一个机器都抢到了一个,那依旧完犊子了呀。
结论:集群模式下synchronized不可取。
2.2、Lock锁
相比synchronized而言,这个锁是方法,而synchronized是关键字。使用lock的实现ReentrantLock
改造代码如下:
继续在模拟集群下进行测试,结果如下:
结果和synchronized效果一样,只有在单机模式下可以保证没问题,而集群模式下依然会出现问题。
结论:集群模式下Lock锁不可取。
二、集群模式下高并发问题
上面讲了单机模式下可以采用的方式解决并发问题,但是有些方式在集群模式下就不可用了,下面就试一下在集群模式下依旧可以解决并发问题的方法。
还是先看看不做任何处理的集群下进行抢商品是什么情况?
简直是炸裂,这样上线不被领导怼着鼻子 。
那我们怎么改造呢?我们引入Redisson。
我们直接使用前面整合布隆过滤网的demo,就不讲整合Redisson了,已经讲过了,直接这里使用。
改造后的代码如下:
jmeter执行后的结果如下:
三台机器只有一台抢到了一个苹果,达到了目的。Redisson的这个分布式锁的使用也很简单,如果服务挂掉,无法执行final的代码会如何,如下看看:
我们打个断点假设服务在获取锁后服务挂了,redis如下:
可10秒后,如下:
锁已经过期失效不见了。因此并不会导致死锁的发生,这个分布式锁的具体实现大佬们可以评论区交流谈论或者后面再继续说。