秒杀如何设计
秒杀难点:
1、突发流量、数据热点
2、数据一致性、短暂混沌态
如果采用传统的数据库进行数据存储,对同一资源的争抢,就会面临严重的锁冲突问题。一般是通过一个前置的,速度更快的存储顶在前面,这就涉及到源库和目标库的数据同步问题。
从商品资源的上架,到秒杀的完成,会经历一个短暂的混沌状态,出现数据不一致的情况。在请求量非常集中的情况下,还会产生并发问题,个体的行为和结果,是不可预测的。
系统层面,秒杀业务如何设计?
客户端,app或者浏览器
站点层,访问后端数据
服务层,屏蔽底层数据细节,提供数据访问
数据层,DB存储库存,当然也有缓存
这四层分别应该如何优化呢?
一、客户端上的请求拦截
1.可以限制用户在n秒之内只能提交一次请求
2.点击以后按钮变为不可点击状态
3.增加倒计时等
4.浏览器缓存,增加秒杀答题,防止有秒杀器
二、站点层的设计
秒杀类业务都需要登录,用uid就能标识用户。
在站点层,对同一个uid的请求进行计数和限速,例如:一个uid,5秒只准透过1个请求,这样又能拦住99%的循环请求。
一个uid,5s只透过一个请求,其余的请求怎么办?
缓存,页面缓存,5秒内到达站点层的其他请求,均返回上次返回的页面。
假设真实有效的请求数是每秒100w,这部分的压力怎么处理?
解决方向有两个:
(1)站点层水平扩展,通过加机器扩容,一台抗5000,200台搞定;
(2)服务降级,抛弃请求,例如抛弃50%;
站点层限速,是个每个uid的请求计数放到redis里么?吞吐量很大情况下,高并发访问redis,网络带宽会不会成为瓶颈?
同一个uid计数与限速,如果担心访问redis带宽成为瓶颈,可以这么优化:
(1)计数直接放在内存,这样就省去了网络请求;
(2)在nginx层做7层均衡,让一个uid的请求落到同一个机器上;
系统隔离
秒杀业务占用的系统资源,和正常运行的系统严重不对等。如果有条件,秒杀系统的硬件和服务环境,要与正常业务系统进行一定程度的隔离。要提前评估对其他服务的压力,避免影响正常业务。
CDN
对于html,js,css,图片等内容,占用了大量的带宽。如果将这些资源都放在自己的服务器上,流量到来时会迅速占满带宽,造成正常的秒杀请求无法完成。CDN可以有效解决这个问题。剩下的请求,就是真正的秒杀业务。
减小请求包
对网络请求包,要进行大量优化。可以开gzip压缩,资源本身也进行压缩,去掉请求包中的无用信息,对网络报文进行精简。
三、服务层的设计
并发的请求已经到了服务层,如何进拦截?
服务层非常清楚业务的库存,非常清楚数据库的抗压能力,可以根据这两者进行削峰限速。
例如,业务服务很清楚的知道,一个商品的秒杀总共只有100个商品,此时透传10w个请求去数据库,是没有意义的。
用什么削峰?
请求队列。
对于写请求,做请求队列,每次只透传有限的写请求去数据层。
只有100个商品,即使10w个请求过来,也只透传100个去访问数据库:
如果前一批请求均成功,再放下一批
如果前一批请求秒杀结束,则后续请求全部返回“秒杀完成”
对于读请求,怎么优化?
cache抗,不管是memcached还是redis,单机抗个每秒10w应该都是没什么问题的。
如此削峰限流,只有非常少的写请求,和非常少的读缓存mis的请求会透到数据层去,又有99%的请求被拦住了。
可以对秒杀产品做数据预热
安全
要尽量提高作弊门槛,比如url动态化;验证码,只会增加攻击者的成本。有些安全性级别较高的,还会增加风控规则,比如同一ip请求过多封禁、账号注册日期三天之内不允许参与、秒杀的门槛必须是金牌会员等。
秒杀前5分钟,验证码升级等。
四、数据库层
经过前三层的优化:
客户端拦截了80%请求
站点层拦截了99%请求,并做了页面缓存
服务层根据业务库存,以及数据库抗压能力,做了写请求队列与数据缓存
你会发现,每次透到数据库层的请求都是可控的。
解决超卖
1. 在系统初始化时,将商品的库存数量加载到Redis缓存中;
2. 接收到秒杀请求时,在Redis中进行预减库存,当Redis中的库存不足时,直接返回秒杀失败,否则继续进行第3步;
3. 减掉库存后,下单请求放入异步队列中,返回正在排队中;
4. 服务端异步队列将请求出队,出队成功的请求可以生成秒杀订单,返回秒杀订单详情。
5. 用户在客户端申请秒杀请求后,进行轮询,查看是否秒杀成功,秒杀成功则进入秒杀订单详情,否则秒杀失败。
6. 预留支付时间,超时未支付库存恢复,可以继续购买
解决库存热点key问题
1. 利用二级缓存
比如利用ehcache
,或者一个HashMap
都可以。把热key加载到系统的JVM中。
针对这种热key请求,会直接从jvm中取,而不会走到redis层。
假设此时有十万个针对同一个key的请求过来,如果没有本地缓存,这十万个请求就直接怼到同一台redis上了。
现在假设,你的应用层有50台机器,OK,你也有jvm缓存了。这十万个请求平均分散开来,每个机器有2000个请求,会从JVM中取到value值,然后返回数据。避免了十万个请求怼到同一台redis上的情形。
2. 备份热key
这个方案也很简单。不要让key走到同一台redis上不就行了。我们把这个key,在多个redis上都存一份不就好了。接下来,有热key请求进来的时候,我们就在有备份的redis上随机选取一台,进行访问取值,返回数据。
分享一个比较好的视频讲解:
https://www.imooc.com/video/19875