美文网首页
如何设计一个秒杀系统

如何设计一个秒杀系统

作者: 盼旺 | 来源:发表于2019-09-25 13:05 被阅读0次

    简介

    秒杀系统本质上是一个满足大并发、高性能和高可用的分布式系统

    原则

    高可用:流量符合预期的时候肯定要稳定,就是超出预期也同样不能掉链子,保证秒杀产品顺利卖出。
    一致性:数据必须一致,即成交总量必须和设定的数量一致。

    注意

    数据尽量少
    请求的数据包括上传给系统的数据和系统返回给用户的数据。少和数据库打交道
    请求数尽量少
    用户请求的页面返回后,浏览器渲染这个页面还要包含其他的额外请求,比如说,这个页面依赖的 CSS/JavaScript、图片,以及 Ajax 请求等等都定义为额外请求,这些额外请求应该尽量少。因为浏览器每发出一个请求都多少会有一些消耗,例如建立连接要做三次握手,有的时候有页面依赖或者连接数限制,一些请求(例如 JavaScript)还需要串行加载等。另外,如果不同请求的域名不一样的话,还涉及这些域名的 DNS 解析,可能会耗时更久。所以你要记住的是,减少请求数可以显著减少以上这些因素导致的资源消耗。
    例如,减少请求数最常用的一个实践就是合并 CSS 和 JavaScript文件,把多个JavaScript文件合并成一个文件,在 URL 中用逗号隔开https://g.xxx.com/tm/xx-b/4.0.94/mods/??module-preview/index.xtpl.js,module-jhs/index.xtpl.js,module-focus/index.xtpl.js。这种方式在服务端仍然是单个文件各自存放,只是服务端会有一个组件解析这个 URL,然后动态把这些文件合并起来一起返回。
    路径要尽量短
    路径指的是用户发出请求到返回数据这个过程中需要经过的中间节点的数量。
    把远程过程调用 (RPC)变成 JVM 内部之间的方法调用
    依赖要尽量少
    所谓依赖,指的是要完成一次用户请求必须依赖的系统或者服务.
    举个例子,比如说你要展示秒杀页面,而这个页面必须强依赖商品信息、用户信息,还有其他如优惠券、成交列表等这些对秒杀不是非要不可的信息(弱依赖),这些弱依赖在紧急情况下就可以去掉

    动静分离

    将用户请求的数据(如HTML)划分为动态数据和静态数据。而动态静态数据的划分,在于看页面中输出的数据是否和URL,浏览者,时间,地域相关,以及是否含有Cookie等私密数据。并不是说数据本身是否动静,而是数据中是否含有和访问者相关的个性化数据。我们就可以对分离出来的静态数据做缓存,有了缓存以后,静态数据的访问效率肯定就提高了。

    如何对静态数据做缓存

    距离用户最近
    常见的,我们可以缓存在:
    用户浏览器
    CDN上
    服务端的Cache中

    浏览器端(js)

    页面静态化:将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素。通过CDN来抗峰值。
    禁止重复提交:用户提交之后按钮置灰,禁止重复提交
    用户限流:在某一时间段内只允许用户提交一次请求,比如可以采取IP限流
    nginx负载均衡,将请求分发到各个服务器,减轻压力。

    服务端控制器层(网关层)

    限制uid(UserID)访问频率:我们上面拦截了浏览器访问的请求,但针对某些恶意攻击或其它插件,在服务端控制层需要针对同一个访问uid,限制访问频率。

    服务层

    采用消息队列缓存请求:既然服务层知道库存只有100台手机,那完全没有必要把100W个请求都传递到数据库啊,那么可以先把这些请求都写到消息队列缓存一下,数据库层订阅消息减库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束。
    利用缓存应对读请求:对类似于12306等购票业务,是典型的读多写少业务,大部分请求是查询请求,所以可以利用缓存分担数据库压力。
    利用缓存应对写请求:缓存也是可以应对写请求的,比如我们就可以把数据库中的库存数据转移到Redis缓存中,所有减库存操作都在Redis中进行,然后再通过后台进程把Redis中的用户秒杀请求同步到数据库中。

    流量削峰

    1.一个是通过队列来缓冲请求,即控制请求的发出;
    2.一个是通过答题来延长请求发出的时间,在请求发出后承接请求时进行控制,最后再对不符合条件的请求进行过滤
    3.最后一种是对请求进行分层过滤。

    减库存设计的核心逻辑

    下单减库存
    付款减库存
    预扣库存
    对于一般业务系统而言,一般是预扣库存的方案,超出有效付款时间订单就会自动释放。而对于秒杀场景,一般采用下单减库存

    如果有竞争对手通过恶意下单的方式将该卖家的商品全部下单,让这款商品的库存减为零,那么这款商品就不能正常售卖了。
    要知道,这些恶意下单的人是不会真正付款的,这正是“下单减库存”方式的不足之处。

    保证数据库中的库存字段值不能为负数,一般我们有多种解决方案:
    一种是在应用程序中通过事务来判断,即保证减后库存不能为负数,否则就回滚

    1:事务+行锁(悲观锁)
    for update:这是数据库行锁,也是我们常用的悲观锁,可用于针对某商品的秒杀操作,但是当出现主键索引和非主键索引同时等待对方时,会造成数据库死锁
     select * from goods where ID=1 for update
    for update 仅适用于InnoDB,并且必须开启事务,在begin与commit之间才生效
    

    另一种办法是直接设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时会直接执行 SQL 语句来报错

    2:设置无符号:性能是1种的三倍  try sql语句 当库存不够时catch捕捉错误 返回数量不足
    

    再有一种就是使用 CASE WHEN 判断语句,例如这样的 SQL 语句:

    3:乐观锁:先比较在更新 
    
       方式1:case:该方式在库存小于购买商品数量时会冗余的更新一遍库存且不能正确获取是否扣减成功,所以还得查询一次数据库:
       UPDATE goods SET store = CASE WHEN store>= num THEN store-num ELSE store END
    
      方式2:where语句添加条件判断:性能又略微优于设置无符号
      UPDATE goods SET store =  store-num where id=1 and store>num
    

    问题:

    1.如果队列处理失败,如何处理?肉鸡把队列被撑爆了怎么办?
    答:处理失败返回下单失败,让用户再试。队列成本很低,爆了很难吧。最坏的情况下,缓存了若干请求之后,后续请求都直接返回“失败”(队列里已经有100w请求了,都等着,再接受请求也没有意义了)
    2.秒杀之后的支付完成,以及未支付取消占位,如何对剩余库存做及时的控制更新?
    答:数据库里一个状态,未支付。如果超过时间,例如45分钟,库存会重新会恢复(大家熟知的“回仓”),给我们抢票的启示是,开动秒杀后,45分钟之后再试试看,说不定又有票哟~
    参考:https://zhuanlan.zhihu.com/p/54266957

    相关文章

      网友评论

          本文标题:如何设计一个秒杀系统

          本文链接:https://www.haomeiwen.com/subject/wvyiuctx.html