如何设计一个秒杀系统
许令波
前阿里巴巴高级技术专家
立即订阅
23218 人已学习
课程目录
已完结 9 讲
开篇词 | 秒杀系统架构设计都有哪些关键点?
免费
01 | 设计秒杀系统时应该注意的5个架构原则
02 | 如何才能做好动静分离?有哪些方案可选?
03 | 二八原则:有针对性地处理好系统的“热点数据”
04 | 流量削峰这事应该怎么做?
05 | 影响性能的因素有哪些?又该如何提高系统的性能?
06 | 秒杀系统“减库存”设计的核心逻辑
07 | 准备Plan B:如何设计兜底方案?
08 | 答疑解惑:缓存失效的策略应该怎么定?
如何设计一个秒杀系统
登录|注册

06 | 秒杀系统“减库存”设计的核心逻辑

许令波 2018-10-06
如果要设计一套秒杀系统,那我想你的老板肯定会先对你说:千万不要超卖,这是大前提。
如果你第一次接触秒杀,那你可能还不太理解,库存 100 件就卖 100 件,在数据库里减到 0 就好了啊,这有什么麻烦的?是的,理论上是这样,但是具体到业务场景中,“减库存”就不是这么简单了。
例如,我们平常购物都是这样,看到喜欢的商品然后下单,但并不是每个下单请求你都最后付款了。你说系统是用户下单了就算这个商品卖出去了,还是等到用户真正付款了才算卖出了呢?这的确是个问题!
我们可以先根据减库存是发生在下单阶段还是付款阶段,把减库存做一下划分。

减库存有哪几种方式

在正常的电商平台购物场景中,用户的实际购买过程一般分为两步:下单和付款。你想买一台 iPhone 手机,在商品页面点了“立即购买”按钮,核对信息之后点击“提交订单”,这一步称为下单操作。下单之后,你只有真正完成付款操作才能算真正购买,也就是俗话说的“落袋为安”。
那如果你是架构师,你会在哪个环节完成减库存的操作呢?总结来说,减库存操作一般有如下几个方式:
下单减库存,即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精确的一种,下单时直接通过数据库的事务机制控制商品库存,这样一定不会出现超卖的情况。但是你要知道,有些人下完单可能并不会付款。
付款减库存,即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家。但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了。
预扣库存,这种方式相对复杂一些,买家下单后,库存为其保留一定的时间(如 10 分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买。在买家付款前,系统会校验该订单的库存是否还有保留:如果没有保留,则再次尝试预扣;如果库存不足(也就是预扣失败)则不允许继续付款;如果预扣成功,则完成付款并实际地减去库存。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《如何设计一个秒杀系统》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(88)

  • Null
    兄台,我看过你的书,感觉你的这个主题和你的书一样,都写的相当的泛,尤其是减库存这个地方,更泛,看了之后依然没办法对一些稍微细节的实现处理作出判断,能麻烦写的稍微细点吗?
    2019-06-28
    5
    38
  • 周龙亭
    下单和扣库存两个操作的事务性是怎么做的?

    作者回复: 可以分两步来做,先创建订单但是先不生效,然后减库存,如果减库存成功后再生效订单,否则订单不生效

    2018-10-07
    2
    20
  • 刘小刘
    老师,我觉得你讲的不太明白,你并没有说实际情况下同步是怎样解决并发的,没看到您给的方案,只看到您在评论回复里否定了队列异步处理的方式

    作者回复: 解决的办法就是尽可能避免产生锁,比如根据商品ID进行分库分表设计;再有就是减少锁的粒度例如阿里对MySQL做了定制优化,可以提升MySQL的并发度

    2018-12-21
    1
    9
  • 永光
    老师,你好,
    你提到秒杀商品减库存直接放到缓存系统中实现,也就是直接在缓存中减库存或者在一个带有持久化功能的缓存系统(如 Redis)中完成。这种实现并发读写怎样保持数据一致?以及是不是要用分布式缓存?

    作者回复: 前面有个同学的类似的问题回答过,可以看一下

    2018-10-06
    9
  • shawn
    个人做法,
    针对确定库存,提前下好单,下单人留空,订单短时间内失效
    订单id压入Redis队列,
    请求来到,订单队列lpop,队空则返回失败,
    pop出来的订单补充下单人为当前用户,
    如果订单过期失效则再次下同一商品的空单存入队列

    这个设计可以考虑单个Redis不够用的时候将队列分组,利用轮转或时间戳hash将请求分配到不同队列,

    想问下老师,这个和扣数字库存相比,会不会有更好的并发性能呢?

    作者回复: 说实话,没看出来哪里性能会更好😄
    不过提前下单的思路还比较新颖,你的思路我理解,但是这样就把一个事情分两次来做,会增加了复杂度,有可能导致得不偿失

    2018-10-14
    3
    8
  • 公号-代码荣耀
    许老师好,我有一个想法,只是没有在实践中这样做过,请指教:
    能否借用"数据库水平拆分"的思想?
    具体思路如下:
    库存在数据库的表中就只有一行数据,上面的方案都是对这一条记录进行频繁更新,是非常"热"的热点数据。我们能否将该行数据拆分到不同的数据库中,这些数据库的库存记录之和就是原始库存数量。这样能否会降低数据库的写压力,提高吞吐量?

    作者回复: 实际上,商品都是进行分库分表的,例如根据商品id进行水平拆分

    分库分表就是提高吞吐量

    2018-10-06
    3
    7
  • Coder4
    这种无只能在串行隔离级别才能用吧,不然肯定超售。。。UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END

    作者回复: 数据库层不都是串行操作吗😊

    2018-10-06
    3
    7
  • moliniao
    老师,使用应用排队方式,入队后返回,然后app端轮询请求下单结果吗?

    作者回复: 秒杀web请求一般不用排队,谁先到谁先执行。
    排队一般更多是在服务端的内部请求时发生,而且是在异步请求时通过消息队列来处理

    2018-10-08
    6
  • 一笑奈何
    老师,问下单机mysql 1s内能抗大约多大的QPS? 大约。

    作者回复: 我印象中单实例一般能抗7-8k左右

    2018-10-06
    6
  • 葫芦堂
    许老师,系统支付中遇到一个问题,特向您请教:
    系统说明:系统主要是户外旅游报名,报名成功与否,以支付为准,即「支付减库存」,下单后有效支付时间是15分钟,对接了微信、支付宝支付。
    问题描述:有时出现名额超报1-2名。例如:一个活动还剩2个名额,这时用户A报名2人并下单后选择支付宝去支付(打开支付宝页面前做了人数判断);这时,用户B也报名1人下单后选择微信支付(打开微信页面之前也做了人数判断),结果可能由于网速或其他原因,用户B先支付成功了,接着30秒后,用户A也支付成功了,这样导致最终活动超报1人。
    为此,我看了您讲的「秒杀系统减库存设计逻辑」,还是没有具体解决思路,望您给予指导,谢谢

    作者回复: 第三方支付我不知道有没有提供在真正支付之前有没有回调接口,就是在输了支付密码后再调用一次减库存接口,如果这时失败了就判断没有库存了。但是由于支付系统始终不是内部系统,所以不能方便的做事物控制。所以可能是存在极端情况下的超卖。

    2018-11-02
    2
    4
  • 诗泽
    看到有同学说下单排队可以用请求队列来做,想问一下请求队列里存放的是请求数据吧,即用户请求数据入队列之后请求立即返回,后台异步处理请求数据,那处理的结果如何告知用户?是前端发起轮训请求吗?如果是轮训的话又会占用服务器不少连接资源吧?
    如果请求队列里直接存的是http 请求的话服务器端也是会持有大量未释放的http 长连接。
    所以请教一下实际当中一般请求队列这部分是怎么做的呢?

    作者回复: 大家对请求队列这块问的比较多,后面相关的问题我统一回答一下吧

    2018-10-18
    4
  • Laputa
    请问文中扣库存的case when语句:
    UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END
    当库存不足时,是不是还会执行一次更新操作,即多一次磁盘写操作?
    如果改成这样:
    UPDATE item SET inventory = inventory-xxx where inventory - xxx >= 0
    是否可行?谢谢

    作者回复: 库存不足时也就是秒杀结束时了,即使再有一次磁盘操作问题也不大了
    看这个语句应该也可以

    2018-10-06
    1
    4
  • 宁宁
    下单减库存的方式存在问题是有些用户下单缺不付款,有一个补偿方案就是付款设置超时时间,一旦超时取消订单,同时发送消息到消息对列,库存服务订阅消息,把库存加回去!
    2018-12-06
    3
  • null
    方法一和方法三是不是没啥区别?

    方法一:下单减库存,但是用户不支付,订单超时释放库存
    方法三:预扣库存,用户下单时扣库存,超时释放库存

    区别是不是在“超时”时长?但这个也是人为决定的,所以方法一和方法三是同一种方案?

    作者回复: 如果方法一有后续的超时回补库存,那么就差不多了

    2018-11-01
    3
  • Geek_c19c96
    我们的库存都放在redis里面,读和减库存都在redis里面操作,redis会定时将库存放到mysql中做备份,

    作者回复: 😉

    2018-10-16
    3
  • 我是李香兰小朋友
    “按照商品维度设置队列顺序执行”这句话是什么意思?可以举例说明一下吗?谢谢老师

    作者回复: “按照商品维度设置队列顺序执行”的意思就是,为了防止同一个商品对数据库的操作占用太多的数据库资源,所以采用队列的方式,让其他商品也有公平的机会得到数据的响应,例如如果秒杀的时候,秒杀商品肯定占用大量的请求,数据库的连接池有可能都被秒杀商品占用了,如果不做队列的话,那么其他商品就得不到数据库执行机会了。加入我们分10个队列,那么秒杀商品就会落在这10个队列中的一个,那么最多也就占用机器10分之一的资源。

    2018-10-13
    3
  • 大麦
    秒杀是短时间大量请求,使用下单即锁库存方式,可以通过一个 redis 队列记录下单,一个redis key 记录数量 num,超出的库存下单失败,这样大量请求在 redis 层即可被处理。
    通过 num 与库存的判断来解决无效订单。
    下单端通过队列异步消费下单。
    对于前端,用户下单成功,即进入redis 队列的,响应给前端可以轮询。
    没有的,直接提示抢购失败。

    作者回复: 异步下单的方式,也是一个思路,例如在一些场景下其实已经在使用,例如一些支付场景中,付了款以后,前端页面中会有一个转圈,等个几秒钟再告诉你结果。

    这种方式我个人觉得对用户不太友好,就是要让用户等个几秒钟,而不是像同步的方式能及时得到反馈结果

    2018-10-12
    3
  • Geek_0bf600
    如果并发很大,应用层队列很可能导致内存溢出,何解?

    作者回复: 丢弃☺️

    2019-04-11
    1
    2
  • Geek_626d81
    我又一个想法:就是讲总库存分成几批分别储存在不同的服务器上,比如100个商品分别放在5台服务器abcd,每台放20个商品,用户通过抢购进入网关,我们可以制定一个路由策略比如用户id等于1-100的去a抢购,id等于201-300去b抢购,以此类推,整个抢购活动结束后在整体同步到数据库,这样做减少了数据的并发计算,由于是抢购也不存在单台服务商品库存过剩的情况,您觉得这个思路怎么样?

    作者回复: haha,有意思,不过我们的商品库存本来就是分库分表的,不同的商品库存本身就不在一台机器上的。

    2018-12-21
    2
  • null
    许老师,您好!
    秒杀结束后被释放的库存,如何继续卖?

    例如抢“真香”手机,1000 台秒完后,用户都散了,秒杀结束。这时支付超时所释放的库存,如何处理?不卖了还是继续组织秒杀?

    谢谢!

    作者回复: 如果秒到了,用户不支付,那么剩下的就只能继续卖了😁

    2018-11-01
    2
收起评论
88
返回
顶部