超卖:任何商品都会有数量上限,如何避免成功下订单买到商品的人数不超过商品数量的上限,
二、如何解决
1、前端:
面对高并发的抢购活动,前端常用的三板斧是【扩容】【静态化】【限流】
A:扩容:加机器,这是最简单的方法,通过增加前端池的整体承载量来抗峰值。
B:静态化:将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素。通过CDN来抗峰值。
C:限流:一般都会采用IP级别的限流,即针对某一个IP,限制单位时间内发起请求数量。或者活动入口的时候增加游戏或者问题环节进行消峰操作。
D:有损服务:最后一招,在接近前端池承载能力的水位上限的时候,随机拒绝部分请求来保护活动整体的可用性。
2、后端
I: 首先MySQL自身对于高并发的处理性能就会出现问题,一般来说,MySQL的处理性能会随着并发thread上升而上升,但是到了一定的并发度之后会出现明显的拐点,之后一路下降,最终甚至会比单thread的性能还要差。
II: 其次,超卖的根结在于减库存操作是一个事务操作,需要先select,然后insert,最后update -1。最后这个-1操作是不能出现负数的,但是当多用户在有库存的情况下并发操作,出现负数这是无法避免的。
III:最后,当减库存和高并发碰到一起的时候,由于操作的库存数目在同一行,就会出现争抢InnoDB行锁的问题,导致出现互相等待甚至死锁,从而大大降低MySQL的处理性能,最终导致前端页面出现超时异常。
针对上述问题,如何解决呢? 我们先看眼淘宝的高大上解决方案:
I: 关闭死锁检测,提高并发处理性能。
II:修改源代码,将排队提到进入引擎层前,降低引擎层面的并发度(一点不切实际)
III:组提交,降低server和引擎的交互次数,降低IO消耗。
以上内容可以参考丁奇在DTCC2013上分享的《秒杀场景下MySQL的低效》一文。在文中所有优化都使用后,TPS在高并发下,从原始的150飙升到8.5w,提升近566倍,非常吓人!!!
首先设定一个前提,为了防止超卖现象,所有减库存操作都需要进行一次减后检查,保证减完不能等于负数。(由于MySQL事务的特性,这种方法只能降低超卖的数量,但是不可能完全避免超卖)
update number setx=x-1 where(x-1)>=0;
解决方案1:
将存库从MySQL前移到Redis中,所有的写操作放到内存中,由于Redis中不存在锁故不会出现互相等待,并且由于Redis的写性能和读性能都远高于MySQL,这就解决了高并发下的性能问题。然后通过队列等异步手段,将变化的数据异步写入到DB中。
优点:解决性能问题
缺点:没有解决超卖问题,同时由于异步写入DB,存在某一时刻DB和Redis中数据不一致的风险。
解决方案2:
引入队列,然后将所有写DB操作在单队列中排队,完全串行处理。当达到库存阀值的时候就不在消费队列,并关闭购买功能。这就解决了超卖问题。
优点:解决超卖问题,略微提升性能。
缺点:性能受限于队列处理机处理性能和DB的写入性能中最短的那个,另外多商品同时抢购的时候需要准备多条队列。
解决方案3:
将写操作前移到MC中,同时利用MC的轻量级的锁机制CAS来实现减库存操作。
优点:读写在内存中,操作性能快,引入轻量级锁之后可以保证同一时刻只有一个写入成功,解决减库存问题。
缺点:没有实测,基于CAS的特性不知道高并发下是否会出现大量更新失败?不过加锁之后肯定对并发性能会有影响。
解决方案4:
将提交操作变成两段式,先申请后确认。然后利用Redis的原子自增操作(相比较MySQL的自增来说没有空洞),同时利用Redis的事务特性来发号,保证拿到小于等于库存阀值的号的人都可以成功提交订单。然后数据异步更新到DB中。
优点:解决超卖问题,库存读写都在内存中,故同时解决性能问题。
缺点:由于异步写入DB,可能存在数据不一致。另可能存在少买,也就是如果拿到号的人不真正下订单,可能库存减为0,但是订单数并没有达到库存阀值。
最终解决方案:
我们可以有条件有选择的在读操作上加锁(即在select ...语句最后加上for update),比如可以对库存做一个判断,当库存小于一个量时开始加锁,让购买者排队,这样一来就解决了超卖现象。
三、总结
1、前端三板斧【扩容】【限流】【静态化】
2、后端两条路【内存】+【排队】
https://blog.csdn.net/a_walking_tomcat/article/details/77960203
https://www.cnblogs.com/billyxp/p/3701124.html
网友评论