如何构建一个满足秒杀和大促场景的系统架构

秒杀系统架构设计

    • 什么是秒杀
    • 利用缓存的秒杀架构设计
      • 小库存商品秒杀典型架构
        • 隔离部署
        • 使用缓存保存商品信息
        • 库存扣减
        • 库存的乐观锁实现
        • 商品库存控制业务流
      • 大库存商品秒杀架构

秒杀的架构设计是面试中经常会遇到的题目,笔者在进入目前所在公司时就经历了这样的一次考验,在短短半个小时内需要画出架构图并给面试官讲解,想想还是挺有挑战的,下面记录一下我在后期查阅资料所了解的关于秒杀的架构设计。

什么是秒杀

所谓秒杀,从字面意思上看就是瞬时售空,要满足这个条件,还必须做到低廉价格、大幅推广,只有在满足这两个前提条件,才会形成瞬时售空的场景。
想象一下,海量的买家在同一时间段内对商品进行下单操作,在极短的时间内(通常为1~2秒)商品就进入售罄状态,这是一个秒杀的典型场景。那么该场景下对系统架构有什么要求呢?我们如何做才能满足这些要求?这就是秒杀架构需要解决的问题。

利用缓存的秒杀架构设计

小库存商品秒杀典型架构

不管是X东,还是X多,还是X宝,都经常会有一元秒杀,比如说库存商品为10个,秒杀价格为1元,这是典型的小库存商品秒杀。在这种场景中,商品数量会在极短的时间内降为0,该架构的核心就是处理好商品库存的扣减,不要出现超卖的情况。以下是以阿里巴巴的架构为例说明:
如何构建一个满足秒杀和大促场景的系统架构_第1张图片

隔离部署

图中虚线左侧部署的是秒杀商品的服务实例,右侧部署的是普通商品的服务实例,两者一定要隔离部署,避免因为秒杀活动开始时突然涌入的过大流量影响正常的商品服务,这是一定要做的一项保证。

使用缓存保存商品信息

不知道大家有没有参与过秒杀,在秒杀正式开始前,都会不自觉的刷新页面,以保证自己没有掉线或者因为自己心急。如果每一次的刷新都从数据库获取数据,这对数据库来说将是一种灾难。所以一定要将商品的信息包括库存信息保存在缓存服务器上,每一次刷新获取到的数据都是从缓存中获取,不会到达数据库。

库存扣减

当用户到达付款的流程时(图中的Buy),这时候应该对商品的实际库存进行扣减,扣减成功会实时同步给缓存,接下来用户在秒杀页面看到的就是扣减后的库存信息了。

库存的乐观锁实现

为了避免商品出现超卖(成功下单的商品总数量大于库存数量)的情况,在进行商品库存扣减时需要用到数据库的事务锁机制,保证同一种商品在同一时间不会被两个不同的数据库事务所修改。数据库的事务锁机制有两种,乐观锁悲观锁。在秒杀活动中,商品的库存信息是一个访问频繁的热点数据,如果采用悲观锁(比如select语句带forupdate),会给后续的订单处理带来很大的性能阻塞,所以一般采用的就是乐观锁实现。实现的方式也比较简单,也就是在最后执行库存扣减操作时,将事务开始前获取的库存数量带入到SQL语句中与目前数据库记录中的库存数量进行判断,如果数量相等,则该条更新库存的语句成功执行;如果不相等,则表示该商品的库存信息在当前事务执行过程中已经被其他事务修改,则会放弃该条update的执行,可以采用重试的机制重新执行该事务,避免商品超卖的发生,具体的SQL语句示意如下:

Update auction_auctions set quantity=#inQuantity# where auction_id=#itemId# and quantity=#dbQuantity#

其中#dbQuantity#为事务中在update语句执行前,通过select语句获取到的商品库存数量。

商品库存控制业务流

如何构建一个满足秒杀和大促场景的系统架构_第2张图片
上图中描述了在小库存商品秒杀场景下,用户在通过商品详情页查看商品时,获取的商品基本信息以及库存均是从缓存中获取,如步骤1.1。当用户查看到当前商品还有可卖库存时,进入到Buy商品下单界面,此时商品的相关信息依然还是从缓存获取,如步骤2.1。用户在进行下单操作后,此时就通过数据库本机事务操作的方式,通过商品中心的服务(IC)获取到当前商品真实的库存信息(步骤3.1),当此时获取的库存大于0时,则进行库存的扣减操作(步骤3.2),在通过步骤3.3更新了商品数据库中的库存信息后,同时也会更新缓存中该商品的库存信息(步骤3.4),则前方用户再访问该商品信息时,看到的就是已经更新后的库存信息。
在小库存商品秒杀场景下,用户的操作只有在最终的下单环节才会访问到数据库,其余时间的操作都是从缓存中获取都数据,大大减缓了数据库的压力,而且由于商品的库存少,秒杀活动转瞬间就结束了,采用这样的架构基本能够满足该场景的需要。

大库存商品秒杀架构

所谓大库存商品秒杀,是说库存并不是像小库存那样只有10件或更少,而是比如说5000件纸抽一元秒杀,这种库存数量较大的秒杀场景。在该场景下,库存记录同样是一个访问频繁的热点数据,如果继续采用像小库存秒杀架构中乐观锁的机制,很容易造成大面积重试的情况。想象一下,某一条数据库事务在修改库存时会比较当前库存数量也事务前获取的库存数量是否相等,只有在刚好没有其他事务修改的情况才会如此,否则就会不断的重试。这就会导致用户创建订单长时间的等待,也可能会出现先提交订单的用户反而比后提交订单的用户更晚下单成功,甚至会出现已提交订单的用户因为没有库存而导致下单失败。所以,我们需要用另一种架构来解决这个问题。
为了解决这一类大促场景的需求,则就要进一步发挥缓存产品的威力,将之前仅仅作为商品信息浏览的缓存的作用,提升到为库存操作提供事务支持的角色。
如何构建一个满足秒杀和大促场景的系统架构_第3张图片
在秒杀活动开始前,将该商品的库存加载至缓存。跟小库存商品秒杀架构相同,用户访问商品详情页也是从缓存获取,在库存大于0的情况下,用户进入到下单界面(Buy),并点击“确认购买”按钮进行下单操作时(步骤3),后端处理的逻辑就跟小库存商品秒杀有了较大的区别。
在用户进行了下单操作后,程序首先会为该订单创建一个订单详单记录,只不过在库存成功扣减前,该订单的状态是用户不可见的。保存该订单的信息非常重要的作用是当缓存服务出现故障不可用时,可通过商品数据库中初始缓存的信息和数据库中订单详单信息能还原出订单处理的最新状态,而不至于出现因为缓存的故障造成业务数据的丢失。在数据库中成功创建订单详单后,会发送一个库存修改的请求到消息服务器。因为一般的缓存平台还不支持MVCC或数据锁的功能,此时利用消息服务可让库存修改的请求线性处理,保证在订单详单创建成功后,发送消息到消息服务器,当消息的订阅程序从消息服务器获取到消息后,则对缓存中的库存数据进行更新处理。因为缓存数据更新时间在纳秒级,所以整体的库存处理性能相比传统数据库方式差别至少在百倍。当缓存中的库存数据成功更新后,则将之前创建的订单详情状态修改为成功下单状态,整个订单创建过程结束。
在这个方案中,实际上是将订单交易创建环节中对于原本商品数据库的库存信息操作替换为在缓存服务器中运行,充分展现了缓存服务相比于传统数据库在性能上的巨大优势。而且随着缓存技术的逐步完善和发展,业界各缓存平台的高可用性已经越来越高,从趋势来看,缓存技术将会在互联网应用场景中将扮演越来越重要的角色。

你可能感兴趣的:(架构设计)