秒杀业务的特点:
- 瞬时流量大
- 参与用户多,可秒杀商品数量少
- 请求读多写少
- 秒杀状态转换实时性要求高
秒杀分为三个阶段:
- 活动未开始
仅有两种请求,一是加载活动页信息,二是查询活动状态得到未开始结果。两种请求各占一半。 - 活动进行中
持续非常短,用户大量发起秒杀请求,瞬时秒杀请求占比增高,能不能抗住秒杀请求就是秒杀系统是否能抗住高并发的关键。 - 活动结束
进入结束状态,请求情况同活动开始前。
加载活动页请求
相对静态的内容,通过varnish反向代理,以页面相关参数比如秒杀活动ID和城市ID的hash作为key,把页面缓存在varnish机器上,而秒杀活动的状态等动态信息通过ajax刷新。
达到的效果是活动期间,加载页面请求都会打到varnish机器直接返回,而不会给web和service带来任何压力。
注:Varnish是一款高性能、开源的反向代理服务器和缓存服务器
查询活动状态
秒杀状态有三种:未开始、可抢、已抢完,由两个共同因素决定:①活动开始时间 ②剩余库存
1、读取秒杀状态请求并发非常高,可以加上合适的缓存来处理。对于活动开始时间是一个较固定且不会发生变化的属性,并且同时在线的秒杀活动数目不多,可以选择用响应快的ehcache来缓存。对于库存个数,需要做到全局一致,可以用memcached来缓存。库存个数的缓存没有意义,可以缓存true or false能不能抢,仅需改变一次。
因为秒杀期间查询活动状态的请求都打在memcached上,减少写的频率可以明显减轻memcached的负担。
秒杀请求分析
写请求,不能缓存。
需要两个步骤:1、扣库存;2、反馈是否秒杀成功。
秒杀业务的一个特点是参与人数多,但是可供秒杀的商品少,也就是说只有极少部分的用户最终能够秒杀成功,让过多的sql来竞争是没有意义的,所以要限制这些参与到扣库存这一步的人数。
秒杀队列校验
可抢状态需要第三个因素决定,就是当前秒杀的排队人数。加在判断库存剩余之前,加上一层排队人数的校验, 即有库存 并且 排队人数 < 限制请求数 = 可抢,有库存 并且 排队人数 >= 限制请求数 = 抢完
流程:
发起秒杀先去问排队队列是不是已满,满了直接秒杀失败,同时可以去更新之前缓存了是否可抢 true or false的缓存,直接把前台可抢的状态变为不可抢。没满继续查询库存等后续流程,开始扣库存的时候,把当前用户id入队。 这样,就限制了真正进入秒杀的人数。
重复提交:
如果重复提交的量大,比如放过去的请求中有一半都是重复提交,就会造成最后没秒完的情况,怎么屏蔽重复用户呢? 就要有个地方来记参与的用户id,可以使用redis的set结构来保存,这个时候set的size代表当前排队的用户数,扣库存之前add当前用户id到set,根据add是否成功的结果,来判断是否继续处理请求。
更多的优化
1、分表。首先将库存数放到redis上,再分表,增加并发。
2、异步消息。可以将扣库存和反馈秒杀失败,改为异步。通过消息队列实现。
总结:
先用varnish挡掉了所有的读取状态请求 然后用ehcache缓存活动时间,挡掉活动未开始时查询活动状态的请求 memcached缓存是否可抢的状态,挡掉活动开始后到结束状态的活动查询请求 redis队列挡掉了活动进行中,过量的秒杀请求 到最后只留下了秒杀商品数量级的请求到数据库中。