编辑
2025-05-18
技术学习
00

目录

秒杀系统设计
思路
架构设计
存储设计
整体架构
补充
名额退回
抢购前给Redis增加库存
打击黄牛
写在最后

秒杀系统设计

写在前面: 建站以来,除了上传一些之前的笔记,就没再写新内容了,有点惭愧,也有些偏离了最初的设想。前段时间忙于毕业设计和实习,技术学习一度中断(毕设偏理论,难以落地成工程项目;算法方向也不是特别感兴趣,所以相关内容更新可能会比较少)。 预计七月进入秋招节奏,五一之后开始重新拾起技术栈,补了一些 Java,同时也在陆续复习八股、学习新内容。最近花时间看了一下秒杀系统设计,这是面试中常见的场景问题,顺便做点简单的记录和分享。

思路

秒杀系统主要是做三件事情:

  1. 承接高并发的流量,削峰还是限频?

    削峰就是使用消息队列,这样可以扛下很高的qps,但是产品体验不好,抢了一会发现抢不到。

    所以一般还是使用限频

  2. 库存扣减

    小流量来讲,直接使用Mysql就好,但是对高并发场景,Mysql扛不住,Redis不可靠。所以本次设计使用的方案是Redis预扣

  3. 扣库存 Redis只做预扣使用,即限频作用,只有Redis预扣成功的流量才会到Mysql。而真正的库存放在Mysql中。

    Mysql库存扣减成功后,才会创建订单,前端跳转支付页,支付成功后更新状态。放弃支付的,库存重新更新回去。

架构设计

存储设计

Redis实现预扣,Mysql是实际的库存。也就是说,Redis中拿到了“库存”,也并不代表实际抢购成功,只是拿到了去Mysql获取真实库存的机会(当然这个机会成功的概率是非常高的,一般情况也不会有问题)。库存最终还是受Mysql的保护,如果出现了主从切换或其他问题导致的异常,这时之前拿到名额的人也不一定能够抢购成功。

除了Mysql和Redis,还是用了消息队列来再次保证系统的高可用。虽然说通过Redis拿到名额的流量已经减少了很多,但是如果商品数量(指一个mysql物理分片存储当前时间参与秒杀的所有商品数)过万,那么打到Mysql的瞬时流量也会很高,足以将Mysql打崩。

  • Mysql存储内容

    • 库存表:记录商品库存数量
    • 秒杀记录表:记录用户秒杀操作信息。这里除了用户id、、订单编号等相关信息之外,一定要有一个status字段,用来记录此次操作的状态:预扣成功待生成订单、订单生成待付款、已付款、超时未付款、主动取消等。
  • Redis 存储内容

    • 商品库存剩余量:用于预扣
    • 全局限额:单个用户对某个商品可以秒杀多少件
    • 用户商品秒杀单号:秒杀单号通过发起秒杀请求的返回包里得到,如果有秒杀单号,说明则表示在秒杀流程中,禁止重复发起秒杀流程。(如果限制多单,可以则value可以额外记录其他信息)
    • 用户秒杀状态

整体架构

  1. 获取基本的商品信息和秒杀信息

  2. 发起抢购

    2.1 扣减Redis 名额并记录秒杀信息。要记录秒杀当前状态。

    2.2 发送mq

  3. 消费mq信息

    3.1 扣减Mysql库存 3.2 通过订单系统创建订单 3.3 将秒杀记录写入表中 3.4 更新预扣系统中Redis秒杀信息状态。

  4. 在此流程中,客户端会一直轮询,当发现秒杀状态变为待支付时,跳转支付页面。

另外,流程中对于Mysql的处理,可以通过事务解决原子性,而Redis必须通过lua脚本保证单次操作的原子性。

补充

名额退回

通过上面的设计,几乎可以避免了超卖现象了

  • 名额加载回Redis

    通过定时任务对比Mysql和Redis库存。需要判断Mysql库存还在,且Redis名额为0,此时可能少卖了。为了避免还有请求没有处理,可以等待一段时间,确定少卖了,则将Mysql库存加载回Redis。

  • 超时库存退回Mysql

    定时任务扫描近X分钟的数据(比如15分钟),如果订单超时没有支付,额度加载回Mysql,通过乐观锁+事务实现幂等,此时可以出发上面加载Reids的操作(因为那么久大概率已经没有了)

抢购前给Redis增加库存

一般想法可能是定时任务,比如十点开卖,那么十点启动一个任务去给Redis增加库存。但是仔细思考发现是有问题的,因为这需要时间,给用户的感觉会是开始抢购的时间不准。因此只能通过给Redis预加载库存来实现。

提供一种思路,来源是一个朋友分享的企业内部解决方案。预先给所有秒杀商品库存全部加载进去,比如A商品10点、16点开卖两次,每次1000单。那么可以在0点就将库存加载到Redis预扣库存中,但是在判断是否还有库存不再和0比较,而是10点前和2000比较,10点到16点和1000比较,16点之后和0比较。当然后续名额退回等操作也需要实现类似逻辑。具体实现方法可以自己构思,无论是中间加一层保存各商品不同时间应剩余的库存,还是给每个商品提供一个计算公式,计算库存使用该公式计算,我认为都可以。

打击黄牛

可以针对IP限流、加入验证码(一般不用)

写在最后

系统设计没有银弹,要根据具体情况判断,不同量级不同要求下架构设计一定不同,既要保证系统的高可用、避免超卖、尽量少卖,也应该避免“大炮打蚊子”。对于一些企业来讲,这种系统一定不够,还需要考虑分片分表、Redis以及Mysql集群部署等问题,也需要补充一定的降级方案。总之,本文仅记录自己学习的一点内容,大多数思路来源网上,同时补充了一点自己的思考,如有问题欢迎大家指正。

本文作者:AstralDex

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!