业务描述
现在电商企业一大把,除了某宝、某东等大鳄,还有各种街、某说、某会、没有品等创业明星,以及苏宁、国美等传统企业的电商部门,可谓百花齐放。电商以运营为基础,各种活动、购物节层出不穷,秒杀可谓是其中的一朵奇葩——价格低到不要不要的。由于好货超低价(大多是一元),秒杀会吸引大量的尖峰流量聚集,对架构师来说是一大挑战。 只谈技术上的业务描述,秒杀一般分为秒杀前、秒杀中、秒杀后三个阶段,各个阶段的需求如下:
秒杀前,用户访问秒杀宝贝描述页,不断刷新,等待秒杀开始。
秒杀中,用户点击秒杀按钮,洪水般的流量瞬间涌入系统,都想走在前面,抢到宝贝。
秒杀后,秒杀结束,商品已被抢完或者秒杀时间已过,用户访问秒杀结束页面,整个秒杀阶段结束。
挑战在哪
秒杀前,大量用户频发刷新秒杀页面,都想第一个看到秒杀开始,越临近开始时间,刷新越频繁,这时候对系统会有巨大的读压力。 秒杀中,用户的秒杀请求短时间内持续涌入,前端访问量暴增,并且会牵扯到写库的需求,还有在对于单一热点商品库存判断和递减,由于写库是排他操作,一个一个排队处理,数据库写压力很大。 秒杀后,和秒杀前差不多,短时间内单页面很多用户访问,但时间一过,页面就没有访问量了。
所以,方案呢
对于秒杀前后单页面的大量访问,处理起来很简单,提前预热把秒杀页面放入cdn,大量的请求就可以抗住了。这里有一些需要注意的点,一是cdn放的是静态页面,秒杀需要判断秒杀开始,这个可以使用动态加载js的方式(网页),从cdn去请求一个js(秒杀开始后再生成),该js负责跳转到秒杀页面(秒杀开始后再生成,避免作弊),如果是app,可以用webview或者请求一个cdn上的文件也是可以的。有人说为什么不直接请求接口来获取秒杀开始标识,当然是可以的,但是cdn是部署到全国各地的,如果请求服务器,这部分量会占用一部分服务器资源,秒杀诶,能节约资源来进行后续请求就节约啊,不然挂了不好看啊。二是怎么判断秒杀是否开始,秒杀基本都是约定好在某个时间点开始,所以一种直接的想法就是靠时间咯。这的确是一种做法,但不是很好,首先是因为秒杀为了分散量,多个商品的秒杀就算是一个时间点,也应该分开进行,这样缓解系统压力;其次就是为了公平性,这个开始时间会在一定范围内有随机性,这样由于用户看到页面的时间随机,不用只拼网速啦,人品也是有用的;还有就是秒杀开始后还有其他工作要做,比如刷cdn放js,生成秒杀页面等,时间在分布式系统里是最不靠谱的了(服务器时间是有差别的),会导致数据问题(比如js生成了,秒杀页面还没有出来,用户一堆404),用户会骂娘的。所以一般会抽象一个发令器出来(是的,程序来源于生活。。。),秒杀系统激活发令器,秒杀开始,下游其它业务开始处理,这里可以定义这些业务的处理顺序,秒杀系统里可以配置何时发令,当然也可以有随机的规则。消息中间件出现了,这里发令器发令后,下游的业务就是通过消息中间件得到的秒杀开始的信号!!很多解决不了的问题往往是因为视角不对,以更高的视角看问题,很多问题往往只缺一个抽象。
秒杀中的最大压力就是对商品库存的并发争抢,就像商场大促销,就那么两件,一堆大妈冲上去抢,各个身怀绝技,商场导购小妹没点武艺还真是抗不住。这时候可以参考之前文章中的异步减库存的形式来做,只是这里是异步下订单。说具体方案之前,先看看这个业务场景要做什么?其实就是在解决一万个人同时拿着钱来东西,但是东西只有十件,你卖给谁的问题,只要没给钱,那我东西不给你就没问题,给了钱就必须拿到东西。所以这里我们可以对来买东西的人强制他们排队,先到先得,并且排队的数据少量丢失是可以接受的,后面拍着那么多人呢,不怕卖不完。那最后的方案就是,用户点击秒杀按钮后,加入秒杀队列(限制长度),加入成功则告知正在秒杀,否则直接跳到秒杀结束页面。对于秒杀队列中的用户,则进行写订单减库存处理处理,并且记录成功与失败,商品秒杀完成后,标记秒杀结束,并且可以清空秒杀队列,避免后面重复判断和处理。用户端轮询(可以采用长轮询,时效性好)结果,如果秒杀结束并且自己没有秒杀成功,则跳转到秒杀结束页面,否则继续等待,如果秒杀成功则跳转到秒杀成功页面,支付就是后面的事情了。这里有几个点需要关注,一是作弊,就是验证码,各种策略各种造型的验证码;二是时效性,秒杀完后一段时间(比如半个小时)没付款,应该将该笔秒杀标记无效,并且把库存回补。消息中间件再次大显身手,秒杀队列,这里需要消息中间件提供限长队列功能(不限长,先查询再入队也是可以的,只是要把握好这个时间间隔可能加入的多余数据对消息中间件的影响),清空队列功能(没有也可以,只是不能更快的释放压力)。当然除了这些,还有限流功能,接入层只允许系统可负载流量进入,超过一定负载就采取措施缓解流量进入(比如验证码。。。)。可以看到,对于秒杀中功能,基本就是采取逐级降低流量的方法(大流量系统的重要思路),只让不过分高于有效流量的流量进入后端真正的业务系统,保证业务系统不宕机。还有一个原则就是读比写更容易扩展,无业务系统比业务系统更容易扩展,所以应该化写为读(下订单变为查询订单),降低业务系统压力(前端接入层拦截无效流量)。
秒杀后的方案和秒杀前差不多,不再赘述。
最后说两句
本质,本质,本质! 抓住本质看问题,不要为了解决问题而解决问题!
本文首发来自架构小站——浅谈秒杀系统