最近业务试水电商,接了一个秒杀的活。以前常常看到淘宝的同行们讨论秒杀,讨论电商,此次终于轮到咱们本身理论结合实际一次了。前端
ps:进入正文前先说一点我的感觉,以前看淘宝的ppt感受都懂了,等到本身出解决方案的时候发现仍是有不少想不到的地方其实都没懂,再次验证了“细节是魔鬼”的理论。而且一我的的能力有限,只有你们一块儿讨论才能想的更周全,更细致。好了,闲话少说,下面进入正文。数据库
1、秒杀带来了什么?后端
秒杀或抢购活动通常会通过【预定】【抢订单】【支付】这3个大环节,而其中【抢订单】这个环节是最考验业务提供方的抗压能力的。架构
抢订单环节通常会带来2个问题:并发
一、高并发异步
比较火热的秒杀在线人数都是10w起的,如此之高的在线人数对于网站架构从前到后都是一种考验。高并发
二、超卖性能
任何商品都会有数量上限,如何避免成功下订单买到商品的人数不超过商品数量的上限,这是每一个抢购活动都要面临的难题。优化
2、如何解决?网站
首先,产品解决方案咱们就不予讨论了。咱们只讨论技术解决方案
一、前端
面对高并发的抢购活动,前端经常使用的三板斧是【扩容】【静态化】【限流】
A:扩容
加机器,这是最简单的方法,经过增长前端池的总体承载量来抗峰值。
B:静态化
将活动页面上的全部能够静态的元素所有静态化,并尽可能减小动态元素。经过CDN来抗峰值。
C:限流
通常都会采用IP级别的限流,即针对某一个IP,限制单位时间内发起请求数量。
或者活动入口的时候增长游戏或者问题环节进行消峰操做。
D:有损服务
最后一招,在接近前端池承载能力的水位上限的时候,随机拒绝部分请求来保护活动总体的可用性。
二、后端
那么后端的数据库在高并发和超卖下会遇到什么问题呢?主要会有以下3个问题:(主要讨论写的问题,读的问题经过增长cache能够很容易的解决)
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 set x=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,可是订单数并无达到库存阀值。
3、总结
一、前端三板斧【扩容】【限流】【静态化】
二、后端两条路【内存】+【排队】
4、非技术感想
一、团队的力量是无穷的,各类各样的解决方案(先不谈可行性)都是在小伙伴们七嘴八舌中讨论出来的。咱们须要让全部人都发出本身的声音,不要着急去否认。
二、优化须要从总体层面去思考,不要只纠结于本身负责的部分,若是只盯着一个点思考,最后极可能就走进死胡同中了。
三、有不少东西觉得读过了就懂了,其实否则。依然仍是须要实践,不然别人的知识永远不可能变成本身的。
四、多思考为何,会发生什么,不要想固然。只有这样才能深刻进去,而不是留在表面。
ps:以上仅仅是咱们讨论的一些方案设想,欢迎你们一块儿讨论各类可行方案。