本人从事电商行业十余年,经历过上百次的促销活动和限时抢购。我每做一次限时抢购,瞬间的流量就会增加几十倍,甚至上百倍。这是对系统架构的一次巨大考验,期间我们经历了系统宕机,甚至出现整体雪崩。图片来自Pexels,那么我们如何设计秒杀系统,才能保证秒杀系统的高性能和稳定性,同时保证日常业务不受影响呢?先来看看秒杀场景的特点:秒杀开始前几分钟,大量用户开始进入秒杀商品详情页,很多人开始频繁刷新秒杀商品详情页,数量秒杀产品详情页的访问量将急剧增加。限时抢购开始后,大量用户开始抢购。这时候,当订单创建时,扣库存的压力会明显增加。其实秒杀的场景基本上就是秒杀的参与者很多,但是秒杀成功的人却很少,往往是几十万人甚至更多的人抢上百个商品股。那么我们是如何设计秒杀系统的呢?主要涉及以下几个方面:对秒杀业务流程的思考由于参与秒杀的产品售价很低,基本上是“拿来就赚”。不付款的情况非常罕见。因此,我们采取下单减库存的方案,下单时扣除库存,再付款。个别订单不付款怎么办?没关系。闪购活动的主要目的是吸引流量,个别订单不付款对闪购活动本身影响不大。而且,剩余未支付的存货,可以继续作为普通商品销售。但是要注意机器人和自动化脚本的防御,后面会详细介绍。该页面是静态的。”秒杀开始前几分钟,大量用户开始进入秒杀产品详情页,很多人开始频繁刷新秒杀产品详情页。此时,秒杀产品的访问量详情页面会急剧增加。”如果所有的请求都发给后端服务,那么后端服务的压力会很大(后端服务需要处理业务逻辑和访问数据库,吞吐量比较低)。考虑到秒杀是运营学员提前安排的活动,所以在秒杀活动开始前就已经确定了要秒杀的产品、商品价格等信息。所以我们可以把秒杀商品详情页面做成一个静态页面,把所有的商品详情、商品价格等参数、评论评价等信息都放在这个静态页面上,然后把这个静态页面上传到CDN进行预热。CDN是内容分发网络,可以简单理解为互联网上的一个巨大的缓存,用来存储静态页面、图片、视频等,可以显着提高访问速度,利用CDN来承载流量,所以大量的商品详情页访问请求不需要访问自己的网站(源站)。这样既可以提高访问速度,又不会增加网站的压力,还可以减轻网站的带宽压力。请求拦截前端页面,点击相关按钮置灰,防止重复提交。网关(Zuul、Nginx)层,为了避免前端恶意请求,比如一些攻击脚本,在网关层,ordering等接口要通过userID进行限制,几秒内只能访问一次.考虑到秒杀场景的参与者较多,秒杀成功的人很少,我们可以在网关层直接拒绝大部分抢购订单请求,将其视为秒杀失败。这大大减轻了后端服务的压力。假设秒杀库存为200,我们只能向后端服务释放200个请求。需要注意的是,为了尽可能避免库存被机器人和自动脚本抢走,秒杀开始时200个请求不能同时释放,可以分阶段释放。比如秒杀启动后,100ms内随机选择5个请求释放(100ms内其他请求直接拒绝,视为秒杀失败),然后每100ms释放5个请求,可以释放200个请求在4秒内。分段发布,除了限制机器人和自动脚本,将请求分散在各个时间段,进一步缓解后端服务压力。分段发布的总时间不宜过长。如果每100ms释放一个请求,则需要20秒才能释放所有200个请求。这样一来,用户就会明显感知到,早下单的人没有闪杀成功,而晚下单的人反而秒杀成功,用户体验就会变差。另外,秒杀过程中对网关的压力也会比较大。网关可以做成集群,多个节点分担访问压力。后端服务设计如果库存只有200个闪购,被网关拦截后,加上分段发布的方式,后端服务基本没有压力,日常的后端服务可以全力支持闪购活动。无需更复杂的设计。但是,如果有几万个闪购库存,几万个订单要发布,总的发布时间不能太长,影响用户体验,后端服务应该怎么设计?这时候主要压力在数据库上,扣库存压力,创造订单压力。库存可以放在Reids缓存中,提高扣库存的吞吐量。热门商品的库存可以存储在Redis分片中。创建订单可以通过异步消息队列。后端服务收到订单请求,直接放入消息队列。监听服务获取到消息后,首先将订单信息写入Redis,每隔100ms或累计100个订单时,将订单信息批量写入数据库。在前端页面下单后,会定时从后台拉取订单信息,获取订单信息后,跳转到支付页面。采用这种批量异步写入数据库的方式,大大降低了数据库写入的频率,从而显着降低了订单数据库的写入压力。IsolationBusinessIsolation从业务上将闪购与日常销售区分开来,将闪购视为一种营销活动。想要参加闪购的产品需要提前报名参加活动。这样我们就可以提前知道哪些商家、哪些商品会参与秒杀。我们可以根据提交的产品提前生成静态页面,上传到CDN进行预热。提交的产品库存也需要提前预热。预热Redis,避免秒杀启动后大量缓存穿透。部署隔离flashkill相关业务,日常业务分组部署,日常销售业务不能因flashkill问题影响。可以申请一个单独的秒杀域名,从网络入口层开始分流。网关也是单独部署的,秒杀带走自己单独的网关,避免日常网关受到影响。秒杀可以复用订单、库存、支付等日常业务,只需要做一些小的修改(比如下单流程使用消息队列,批量写入订单库,在Redis中扣除库存)。数据隔离为了不让秒杀活动影响日常销售业务,需要单独部署Redis缓存,甚至需要单独部署数据库!数据隔离后,闪购剩余库存怎么办?限时抢购活动结束后,剩余库存可返还至日常库存,继续对百货进行售卖。数据隔离后,秒杀订单和日单不在同一个数据库,后续订单查询如何显示?创建秒杀订单后可以向消息队列发送消息,每日订单服务使用pull方式消费消息。这时候,日常订单服务就是主动方。线程池可以根据机器的性能来增加或减少线程池的大小,控制拉取消息的速度,控制订单数据库的写入压力。网络秒杀前,需要提前向网络运营商和CDN服务商申请带宽。还有哪些细节需要考虑:①如何避免超卖?如果在Redis中扣减库存,可以使用decr命令扣减库存。decr是一个原子操作,在分布式环境下不会有并发问题。decr扣除库存最后判断返回值。如果返回值小于0,则扣存失败,秒杀也失败。如果在数据库中扣除库存,可以在where后面加上库存大于0的条件,避免库存减为负值。这样,就可以避免出现超卖情况。②接口防刷,前面说到在网关层,根据userID限制下单等接口。③网关层不仅限制了userID,还限制了整体流量。当实际访问量超过预估访问量时,整体流量限制可以起到保护作用,防止系统不堪重负。④防止重复下单,根据userID限流起到了防止重复下单的作用。如果限制同一用户10分钟内下单,一般情况下,10分钟内商品已经售罄,该用户将没有机会再次下单。⑤可结合风控系统,在网关层直接拒绝羊毛党等问题用户的请求。⑥可在网关层之上增加防火墙或高防服务,抵御DDos等分布式网络攻击。
