《Hello,World公开课》茅台秒杀背后的架构原理你知道么?

开课吧开课吧锤锤2021-03-24 16:58

    各大电商平台的飞天茅台秒杀活动相信大家都不陌生,线下店铺动辄2000+的53度飞天茅台,只需1499就能买到。不知大家有没有抢到过,小开我是预约了十几次,但是没有一次能抢到过。

    那么这种秒杀背后的技术原理是怎样的呢?短时间内面对巨大的流量,如何防止少卖和超卖的现象发生呢?3月23日,开课吧《Hello,World公开课》邀请到某大厂架构师jessen老师,让他带我们一探秒杀系统背后的架构原理。

    秒杀系统业务特点

    在了解秒杀系统架构原理前,我们先来分析下秒杀的业务场景和特点,这有助于了解背后的技术实现原理。

    所谓的秒杀在指定时间点,对指定的商品通过各种营销方式举行的抢购活动,目的是增加商家知名度、获取新的客户等。它有如下的特点

    1、商品价格一般都远低于市场价

    2、商品一般都是爆款

    3、有购买数量、购买次数等限制

    4、商品的库存是所有用户共享的,且商品总量是有限的

    想想我们抢茅台的时候是不是也是这样。下面我们来看看,针对这种业务场景,它在技术实现上有什么特点和难点

    业务实现特点

    可以短时间的少卖

    大部分场景里用户一次只能够购买一个商品

    用户希望购物流程尽可能的简单

    抢购前,用户很焦急,不断刷新页面

    难点

    短时间内,会有大量用户及流量涌入系统

    不能够超卖,因为会导致商家无法发货,用户投诉

    会有很多无效流量

    如何实现秒杀的真正营销目的

    秒杀系统整体架构

    回忆下我们日常剁手的流程,是不是先浏览商品,加入购物车,提交订单。提交订单前系统会确认是否有库存,然后完成整个购买流程。

    但这个流程其实是有明显缺点的,首先是步骤较长,每次抢购商品都需要从商品页面跳转到购物车,再到订单。其次内容(静态和动态内容)每次请求都从服务端获取,性能太慢且对服务端的压力也非常大。

    要知道像茅台这样的活动,面对的可能都是百万级的流量,这明显不能满足业务需求,所以该如何优化这个流程呢。我们来看下图,主要是做了以下几个方面的改变

    1、使用CDN和浏览器本地缓存,提升性能

    2、缩短步骤,将秒杀的购物流程缩短成浏览商品和直接购买

    3、在部署上,可以更进一步,将秒杀购买页和正常购物的后端应用进行纯隔离

  Java

    订单是用户间的数据插入,没有热点并发问题。且实际产生的订单量也较少,可以依赖分库分表解决,但依然存在一个致命问题。

    所有的秒杀请求都会经过库存系统,进行判断是否有货且数量是否充足。因此,库存需要解决热点数据的并发扣减问题。这个我们留在后文再说,接下来我们看下如何利用三种方案来实现秒杀

    三种秒杀实现方案

    数据库实现方案

    谈到纯数据库实现方案,你首先想到是什么?秒杀或正常的提单请求,sku走扣减服务再读写库存数据库么?设计两个表,一个是库存表,储存商品信息和数量,一个是扣减服务表,储存扣减信息。有多个sku进行扣减时:开启事务保证原子性。可能大多数同学想到的是这种方法。

 Java

    但是这种方式会带来很多问题,我们挑两个最主要的来说

    1、当有多个数量扣减时,一个扣减不成功即回滚?

    2、秒杀开启后,库存瞬间就秒光了,后面的每次请求都需要打库,如何解决影响数据库性能?

    看到数据库性能,有的同学可能会很机智的想到主从分离,根据二八原则,既然读写比例失衡,我配置个从数据库,先抗住读流量再说,这样主从同步改造量最小。没错,这样想是对的,但有没有更优的解决方案?

    这里,我们加入缓存读数据库来抗量,要知道缓存相比数据库至少10倍量级差,采用binlog(有canal、databus等)进行缓存同步。这样同步简单,在不考虑任何miss场景时,如果缓存和对于恶意请求的拦截做到极致,数据库依然扛得起对于并发扣减的请求。

   Java

    纯数据库方案,缺点和优点都是很突出的

    优点:

    强一致性,不会出现超卖和少买。

    使用从库与缓存,读性能有较大提升。可以支持百+/s扣减的tps

    缺点:

    批量扣减,在几十上百个sku时,性能在10S+及以上

    容易出现死锁

    适用场景:

    中等热度的扣减场景

    企业内部、中小站点系统、并发量级小且一致性有强要求

    纯缓存实现方案

    类比纯数据库方案的模型,纯缓存方案就是以redis的数据为准,基于redis的单线程模型保证原子性,使用lua进行多个sku的批量扣减和写流水,实现一次购买多个sku的扣减原子性。再通过异步方式同步缓存里的扣减,将数据刷入数据库,来实现持久化。

    同时,根据二八原则,对于redis我们也做主从数据库的配置。整体架构图如下图所示

  Java

    相较于与纯数据库方案,纯缓存方案优点是性能好,单机能够支持千级以上的tps扣减,不会出现超卖现象。但极限情况会出现丢数据,不能实现最终一致性,所以纯缓存的方案适合非交易类的扣减业务。

    高并发扣减方案

    既然纯数据库与纯缓存都有明显的缺点,那么我们就把两者结合一下,扣减采用数据库+缓存共同的形式。

    在每次的扣减服务时,我们开启一个事务,再扣减缓存并记录到记录库,记录库只会insert插入操作,记录一个扣减的详细json大字符串。再以异步的方式,数据同步work将json的非结构数据转为业务结构数据。但当事务回滚时,而且redis归还失败,会出现短时的少卖情况。整体方案架构如下图所示

    Java

    上面的方案提到了一个数据同步work的模块,那么在应对大规模流量的场景下,如何保证这个数据同步work的效率呢,这里我们介绍一致性hash的方法。

    任务写入记录库基于crc、md5等生产int范围内的随机id。work分布式部署,采用一致性hash分配任务,提升并发同时减少锁冲突。

   Java

    这里可能有同学会问,扣减任务的数量持续飙升如何处理?这里大家要记住,记录库是无状态存储,这就意味着我们可以将它扩容为集群来提高它的存储量和并发性。hash随机写入,在理论上可以实现“永不挂机”。

    这种高并发扣减方案不仅性能好,单机能够支持千级以上的tps扣减,而且不会出现超卖,可以实现数据最终一致性。唯一的缺点就是架构复杂,逻辑较重。

    热点应对方案

    还记得我们文章最开始说的么,秒杀的特点是在短时间内很多人在抢同一个商品,假如一件商品有一万件,对应可能有十万人在抢,这种情况我们用上面的扣减方案可以轻松应对,

    但比如去年疫情刚开始期间,可能有上百万人在同时抢口罩,这种情况下,无论是数据库或者缓存都是扛不住这么大的流量的,所以我们要对流量做削峰处理。

    我们通常使用下面四种方式来削减热点流量

    1、根据用户的IP、账号、手机的IMEI、网卡地址等唯一标识,进行防刷限流。可以采用令牌桶、漏桶算法实现。

    2、根据营销目的,比如吸引用户使用会员。可以在第二步过滤时,设置过滤比。比如,有20个请求里,8个请求为会员请求,2个为非会员。其余全过滤。

    3、当流量仍然超过预期,设置等比例的随机过滤

    4、根据每一个存储的压测值,设置最终的请求穿透数量,防止存储宕机

    在对流量削峰的同时,还可以把商品数量分批储存在多个分片中。假如一共有20个商品,我们可以储存在4个分片中,每个分片中储存5个。整体架构如下图所示

    Java

    讲师介绍

    jessen,知名互联网公司技术专家,团队架构负责人,在高并发架构方面有资深经验。

    《Hello,World公开课》是由开课吧推出面向广大开发工程师的免费加餐课,我们希望能搭建起一座桥梁,将业内的最热门的技术,最好的解决方案一起打包免费送给你。无论你是初入职场的应届生,还是准备升职加薪的职场精英,相信这里都有你需要的内容。

    《Hello,World公开课》是由开课吧推出面向广大开发工程师的免费加餐课,我们希望能搭建起一座桥梁,将业内的最热门的技术,最好的解决方案一起打包免费送给你。无论你是初入职场的应届生,还是准备升职加薪的职场精英,相信这里都有你需要的内容。

    更多《Hello,World公开课》尽在开课吧广场动态信息频道!

有用
分享