实战分布式之电商高并发秒杀
何为“秒杀”及其特点
“秒杀”这一业务场景在如今已经不是什么新鲜名词,它本质上属于短时突发性高并发访问问题,业务特点如下:
- 定时触发,流量在瞬间突增
- 秒杀请求中常常只有部分能够成功
- 秒杀商品数量往往有限,不能超卖,但能接受少卖
- 不要求立即返回真实下单结果
分析一下这些秒杀场景的典型特点,我们不难看出,秒杀场景属于典型的高并发场景,对系统的冲击较大。我们来对上述特点进行逐一分析:
1.定时触发,流量在瞬间突增
这不难理解,秒杀活动往往伴随着固定的节日、活动而开展,在某一个确定时间对C端用户开放访问能力,此时往往会出现一个较为明显的请求激增。如:每年“双11”当天0点,淘宝等电商平台访问量基本上会出现明显的请求波峰,这与秒杀的定时性,息息相关。
2.秒杀请求中常常只有部分能够成功
这是肯定的,在库存有限、请求接收较多的情况下,常会存在部分请求处理成功,部分请求处理失败的情况。
如果库存是无限的,也就不存在秒杀这一说了。也正是因为库存有限,平台以此为卖点,采取定时限量售卖的营销策略更能刺激用户进行访问。
一般情况,秒杀场景下的商品售价较平时都有明显的优惠。
3.商品数量往往有限,不能超卖,但能接受少卖
商品数量有限在上文已经说过了,我们聊聊“超卖”、“少卖”的问题。
超卖是不能容忍的情况,如果发生超卖,则属于业务异常了。一般情况下商家只会提供有限数量的商品作为秒杀营销商品,如果超卖,则商家往往面临较为明显的亏损。这在业务上是不能出现的。
少卖则是能够接受的,比如商家提供了10台IPhoneX作为秒杀库存,由于临时减库存或其他原因(往往都是业务上的原因)调整只卖了9台。这对商家而言并没有多大的损失,商家还可以以更高的价格去售卖该多余库存,而且平台因为该秒杀活动还收获了较高的PV/UV,总体而言是没有损失的。
因此我们在设计秒杀架构的时候要以 “超卖零容忍,少卖能兼容” 这一设计原则进行设计。
4.不要求立即返回真实下单结果
由于秒杀业务的特殊性–短时超高并发,因此我们不能按照传统的交易场景进行设计。
传统交易场景下,对于用户的下单请求一般都是同步处理,即同步落库持久化,并同步返回收单结果。
如果我们对秒杀订单采用同步持久化的做法,则系统的吞吐量将基本依赖DB的性能,这在成本上、性能上都有较大压力。因此,我们要在尽量提高系统收单入口吞吐量的同时降低系统开发部署的成本。
“不要求立即返回真实下单结果”,也就是不需要立即持久化,换言之也就是业务流程 “异步化” 。
明确了流程可以异步化,解决的手段就多了。利用缓存、队列、线程池都能实现业务的异步化。这里也反映出
技术是围绕业务运转的,没有业务作为支撑,无论技术多么新颖、其性能多么高,它都无用武之地。
秒杀业务概述
通过对秒杀核心业务流程进行异步化,我们能够将主流程分为收单、下单两个阶段,业务流程概括起来如下:
秒杀流程–收单
- 用户访问秒杀入口,将秒杀请求提交给秒杀平台收单网关,平台对秒杀请求进行前置校验
- 校验通过后,将下单请求通过缓存/队列/线程池等中间层进行提交,在投递完成同时的同时就给用户返回“排队中”
- 对于前置校验失败的下单请求同步返回秒杀下单失败
到此,对用户侧的交互就告一段落。
秒杀流程–下单
下单流程中,平台的压力通过中间层的缓冲其实已经小了很多,之所以会少,一方面是因为在用户下单的同步校验过程中就过滤掉了部分非法请求;另一方面,我们通过在中间层做一些限流、过滤等逻辑对下单请求做限速、压单等操作,将下单请求在内部慢慢消化,尽可能减少流量对平台持久层的冲击。这里其实就体现了中间层
“削峰填谷” 的特点。
基于上述前提,我们简单总结下秒杀下单部分的业务逻辑。
- 秒杀订单服务获取中间层的下单请求,进行真实的下单前校验,这里主要进行库存的真实校验
- 扣减库存(或称锁库存)成功后,发起真实的下单操作。扣减库存(锁库存)与下单操作一般在一个事务域中
- 下单成功后,平台往往会发起消息推送,告知用户下单成功,并引导用户进行支付操作
- 用户一段时间(如:30mins)没有支付,则订单作废,库存恢复,给其他排队中的用户提供购买机会
- 如果用户支付成功,则订单状态更新,订单流转到其他子系统,如:物流系统对该支付成功的处理中订单进行发货等后续处理
到此,基本上就是秒杀业务的核心主流程。
进一步抽象 秒杀请求->中间层->真实下单 这个场景,是不是很像我们经常用到的一种异步业务处理模式?
这就是 “生产者-消费者” 模式。
“生产者-消费者”模式 在进程内,常常通过 阻塞队列 或者 “等待-通知” 等机制实现,在服务之间则往往通过消息队列实现,这也是本次实战所采用的技术实现手段。在后续的实战中,我将通过RocketMQ消息队列,对秒杀下单进行解耦,实现削峰填谷、提高系统吞吐量的目的。此处就不多赘述,到编码部分再详细展开。
秒杀业务流程图解
详细细节在上文中的描述部分已经做过阐述,读者朋友可以配合一起看,这里只对图示流程进行简略总结:
1:用户访问秒杀网关seckill-gateway-service,对感兴趣的商品发起秒杀操作。特别的,对于商品信息,在系统初始化的时候已经加载到seckill-gateway-service。在进行前置库存校验的时候,依据缓存已经做了一次用户下单流量的过滤。
2:网关对秒杀订单进行充分的预校验之后,将秒杀下单消息投递到RocketMQ中,同步向用户返回排队中
3:秒杀订单平台seckill-order-service订阅秒杀下单消息,对消息进行幂等处理,并对商品库存进行真实校验后,进行真实下单操作
本流程为用户通过秒杀网关seckill-gateway-service提供的查单接口对自己下的秒杀订单进行查询跟踪。
总结
由于重点在于秒杀的核心场景,因此上文还是存在有待优化的细节。提出以下建议供大家参考:
1:推荐采用分布式减库存策略:如:使用Redis的decr进行原子减库存。
2:预热库存时,将库存适当调大,防止恶意刷库存导致正常用户不能进行秒杀下单请求。这里要注意只调整缓存中的库存,不能调整商品库中的真实库存,否则会出现 “超卖” 从而导致损失。
3:秒杀接口需要做防刷处理,可以在前端页面通过倒计时方式定时开放接口;通过增加验证码减少下单频率;通过增加下单前收货地址校验、实名认证等方式对僵尸用户进行拦截。