原则:秒杀开始后,避免请求都到达应用服务器和数据库
1. 秒杀开始前,页面静态化
● 秒杀开始前,秒杀页面静态化,静态页面放在CDN,同时向CDN扩容网络带宽
● 秒杀开始前,用户不能下单。
○ 这是通过秒杀开始前,用户访问的都是CDN的静态页面,秒杀开始后,定时服务器会向JS服务器集群发送一个启动秒杀的JS文件,用户刷新秒杀页面时,会从JS服务集群获取到这个JS文件,从而通过JS文件来访问秒杀服务集群。
2. 秒杀开始后,避免请求都到达应用服务器和数据库:队列限流
● 使用缓存减少请求数:秒杀开始后,单位时间内,同一个用户只能请求一次(缓存结果);同一个商品的查询,单位时间内只请求一次(缓存结果)。
● 秒杀开始后,通过队列方式限流。
3. 防止超卖:
● 防止超卖:关键在于减库存:Step1. select库存;Step2. insert; Step3. update-1, 超卖就是-1操作不能为负数。
所以在高并发场景,如果直接访问DB,会出现高并发多线程对DB竞争行锁的情况,甚至会出现死锁。
● 超卖的解决方案:
○ 方案1:用redis替代MySQL做商品的计数器;
○ 方案2:用队列排队的方式购买,当卖完后,告诉后面的用户商品已售完。
4. 部署:秒杀系统独立部署,预估并使用独立带宽。
5. 必要时采用服务降级策略:关闭非重要服务与feature.
================================================
秒杀系统是高并发的经典场景。
秒杀系统的挑战
-
对现有正常业务的冲击:解决方法是对秒杀单独部署。
-
应用服务器和数据库负载压力
1)秒杀开始前:如果所有查看秒杀商品页面的请求(如刷新页面看秒杀是否开始),都到达后端应用服务和数据库,那么将产生极大负载压力。
解决方法是,秒杀页面静态化:将商品描述、商品参数、成交记录、用户评价等全部写入一个静态页面,而不用动态加载。这样用户浏览该秒杀页面时,就不需要访问应用服务器和数据库。
为了减轻服务器压力,可将秒杀的静态页面缓存在CDN,同时也要向CDN服务商租借新增的出口和带宽。
2)秒杀开始后:要避免所有的请求都到达后端应用服务器及数据库
第一、站点层:
查询操作:做页面缓存,同一个用户的请求在单位时间内,返回同一个页面。对于同一个商品的查询,在单位时间内,返回同一个页面。这种缓存策略,可以确保这样的查询请求被拦在站点层,而不会到达后端的应用层和数据库层。
- 突然增加的带宽
带宽可能比平时高1000倍。
解决方法是,跟运营商直接购买和租借带宽。
- 在秒杀开始前,用户不能下单
解决办法是,在秒杀开始前,不让用户能直接访问到秒杀下单的页面,这就需要将秒杀页面的URL动态化,即在秒杀开始前,谁都无法访问到秒杀下单页面的URL。怎样做到呢?
答案是通过javascript脚本控制,在秒杀商品静态页面中加入一个js文件引用,该js文件中加入秒杀是否开始的flag和下单页面的URL的随机数。当秒杀开始时,一个新的Javascript文件并用户浏览器加载。因为该js文件使用随机版本号,并且不被浏览器、CDN、反向代理服务器缓存。
因为这个JavaScript文件非常小,即使每次浏览器刷新都访问js文件服务器,也不会对服务器集群和网络带宽造成太大压力。
5. 商品的超卖问题
商品超卖的问题的关键在于减库存操作是一个事务的操作,即先select, 再insert, 最后update - 1; 最后的 -1 操作后的数是不能出现负数的,无论当时多高的并发。
这个过程如果当到数据库上,那么操作的库存数在同一行,那么就会出现多线程争抢InnoDB行锁的问题,导致出现互相等待甚至死锁。
上述问题,淘宝的解决办法:
1)关闭死锁检测,提供高并发性能
2)修改MySQL源代码,将排队提到进入引擎层前,降低引擎层并发度
2)组提交,降低Server和引擎的交互次数,降低IO消耗
但淘宝的解决方案太高大上了,我们实际中无法使用,所以实际中的解决方案如下:
为了防止超卖,在MySQL减少库存的语句可以这样写:
update number set x=x-1 where (x -1 ) >= 0;
根据MySQL中事务的特性,上述SQL仅能减少超卖的几率,但不能完全杜绝超卖。
解决方案1. 将库存从MySQL前移到redis中,所有的写操作放到内存中,由于redis不存在锁问题,所以不会出现多线程相互等待竞争资源,并且redis的读写性能都远高于redis,解决了高并发下的性能问题。在写完redis后,在通过队列的方式,异步同步到DB中。
优点:性能好
缺点:没有解决超卖问题,由于同时异步写DB,存在某一时刻DB和redis数据不一致的风险。
解决方案2. 引入队列,然后将所有写DB的操作,在单队列中排队,完全串行处理。当达到库存阈值时,就不再消费队列,并关闭购买功能。这就解决了超卖的问题。
优点:解决了超卖问题,略微提升了性能。
缺点:性能受限于队列中生产的速度和DB写入的消费速度哪个快,另外多个商品同时抢购需要多个队列。
-
总结
-
前端:扩容、静态化、限流、服务降级