当前位置:   article > 正文

面试:如何设计一个秒杀系统

面试:如何设计一个秒杀系统

一、秒杀系统的特点

  • 高性能:秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键
  • 一致性:秒杀商品减库存的实现方式同样关键,有限数量的商品在同一时刻被很多倍的请求同时来减库存,在大并发更新的过程中都要保证数据的准确性。
  • 高可用:秒杀时会在一瞬间涌入大量的流量,为了避免系统宕机,保证高可用,需要做好流量限制

二、设计思路

将请求尽量拦截在系统上游,同时对请求进行限流和削峰。大体架构如下
在这里插入图片描述

前端操作思路:

限流:前端答题或验证码,来分散用户的请求
禁止重复提交:限定每个用户发起一次秒杀后,需等待才可以发起另一次请求,从而减少用户的重复请求
本地标记:用户成功秒杀到商品后,将提交按钮置灰,禁止用户再次提交请求
动静分离:将前端静态数据直接缓存到离用户最近的地方,比如用户浏览器、CDN 或者服务端的缓存中

后端操作思路

限流:屏蔽掉无用的流量,允许少部分流量走后端。假设现在库存为 10,有 1000 个购买请求,最终只有 10 个可以成功,99% 的请求都是无效请求。
削峰:秒杀请求在时间上高度集中于某一个时间点,瞬时流量容易压垮系统,因此需要对流量进行削峰处理,缓冲瞬时流量,尽量让服务器对资源进行平缓处理
异步:将同步请求转换为异步请求,来提高并发量,本质也是削峰处理
利用缓存:创建订单时,每次都需要先查询判断库存,只有少部分成功的请求才会创建订单,因此可以将商品信息放在缓存中,减少数据库查询。
负载均衡:利用 Nginx 等使用多个服务器并发处理请求,减少单个服务器压力

防作弊操作:

  • 隐藏秒杀接口:如果秒杀地址直接暴露,在秒杀开始前可能会被恶意用户来刷接口,因此需要在没到秒杀开始时间不能获取秒杀接口,只有秒杀开始了,才返回秒杀地址 url 和验证 MD5,用户拿到这两个数据才可以进行秒杀。
  • 同一个账号多次发出请求:在前端优化的禁止重复提交可以进行优化;也可以使用 Redis 标志位,每个用户的所有请求都尝试在 Redis 中插入一个 userId_secondsKill 标志位,成功插入的才可以执行后续的秒杀逻辑,其他被过滤掉,执行完秒杀逻辑后,删除标志位
  • 多个账号一次性发出多个请求:一般这种请求都来自同一个 IP 地址,可以检测 IP 的请求频率,如果过于频繁则弹出一个验证码
  • 多个账号不同 IP 发起不同请求:这种一般都是僵尸账号,检测账号的活跃度或者等级等信息,来进行限制。比如微博抽奖,用 iphone 的年轻女性用户中奖几率更大。通过用户画像限制僵尸号无法参与秒杀或秒杀不能成功

三、详细说明

秒杀的逻辑

  • 数据库校验库存
  • 扣库存(无锁)
  • 生成订单

详细步骤

服务端的优化

  1. 服务端应该使用乐观锁更新库存,以防止出现商品超卖的问题。

悲观锁虽然可以解决超卖问题,但是加锁的时间可能会很长,会长时间的限制其他用户的访问,导致很多请求等待锁,卡死在这里,如果这种请求很多就会耗尽连接,系统出现异常。乐观锁默认不加锁,更新失败就直接返回抢购失败,可以承受较高并发

  1. Redis 计数限流
    根据前面的优化分析,假设现在有 10 个商品,有 1000 个并发秒杀请求,最终只有 10 个订单会成功创建,也就是说有 990 的请求是无效的,这些无效的请求也会给数据库带来压力,因此可以在在请求落到数据库之前就将无效的请求过滤掉,将并发控制在一个可控的范围,这样落到数据库的压力就小很多

详细的限流算法可以采用令牌桶:
令牌桶算法的流程:

  • 接口限制 t 秒内最大访问次数为 n,则每隔 t/n 秒会放一个 token 到桶中
  • 桶内最多存放 b 个 token,如果 token 到达时令牌桶已经满了,那么这个 token 就会被丢弃
  • 接口请求会先从令牌桶中取 token,拿到 token 则处理接口请求,拿不到 token 则进行限流处理
    因为令牌桶存放了很多令牌,那么大量的突发请求会被执行,但是它不会出现临界问题,在令牌用完之后,令牌是以一个恒定的速率添加到令牌桶中的,因此不能再次发送大量突发请求

在这里插入图片描述
3. Redis 缓存商品库存信息

虽然限流能够过滤掉一些无效的请求,但是还是会有很多请求落在数据库上,实时查询库存的语句被大量调用,对于每个没有被过滤掉的请求,都会去数据库查询库存来判断库存是否充足,对于这个查询可以放在缓存 Redis 中,Redis 的数据是存放在内存中的,速度快很多。
在这里插入图片描述

  • 缓存预热
    在秒杀开始前,需要将秒杀商品信息提前缓存到 Redis 中,这么秒杀开始时则直接从 Redis 中读取,也就是缓存预热,Springboot 中开发者通过 implement ApplicationRunner 来设定 SpringBoot 启动后立即执行的方法.

  • 缓存和数据一致性
    缓存和 DB 的一致性是一个讨论很多的问题,推荐看参考本人的另一篇博客,可以采用简单的cache aside 策略即先把数据存到数据库中,成功后,再去删除缓存或者让缓存失效。也可以使用乐观锁来进行数据库的库存修改

  1. 消息队列削峰
    服务器的资源是恒定的,你用或者不用它的处理能力都是一样的,所以出现峰值的话,很容易导致忙到处理不过来,闲的时候却又没有什么要处理,因此可以通过削峰来延缓用户请求的发出,让服务端处理变得更加平稳。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/84180
推荐阅读
相关标签
  

闽ICP备14008679号