前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >秒杀系统设计

秒杀系统设计

作者头像
leobhao
发布2022-06-28 18:32:00
9400
发布2022-06-28 18:32:00
举报
文章被收录于专栏:涓流涓流

概述

读了极客时间许令波的如何设计秒杀系统后,总结出秒杀系统设计的一些需要注意的点,如何从更多的角度去考量一个架构的设计,保证性能和高可用。

这些经验或者说原则不仅仅适用于秒杀系统,在设计其他系统的时候也有一定的参考性。

秒杀系统的五个原则

总结起来就是:4要,1不要

数据要尽量少

这里的数据值得是用户和系统间传输的数据,包括用户上传给系统的数据和系统返回给用户的数据。

数据少涉及几个方面:

  1. 数据在网络中传输需要时间,数据量越大,网络包耗时越长
  2. 服务器在写网络的时候,一般要进行压缩和字符编码,这些操作比较消耗cpu
  3. 系统依赖的数据要尽量少, 比如和数据库的交互,很容易形成瓶颈
请求数要尽量少

当用户请求页面后,还会有一些其他的额外请求,如静态资源css/js等,每一个请求都会做三次握手,如果资源不在同一个域名下,还会对dns解析形成负担。

可以将多个资源合成一个文件,尽可能对减少请求数

路径要尽量短

路径指的是,用户发出一个请求到返回数据的过程中,经过的中间节点数。

每新增一个节点不但会新增一次网络连接,并且会新增不确定性(多一个节点,就会增加多一个风险点)。缩短请求路径可以增加可用性,也能提升性能。做法一般是将多个互相依赖多应用合并部署在一起,将RPC调用变为本地JVM调用

依赖要尽量少

依赖指的是完成一次用户请求必须依赖的系统或者服务。分为强依赖(必须的依赖)和弱依赖(必要时可以去掉)

如秒杀页面必须依赖商品信息、用户信息,但是其他如优惠券、成交列表等并不是非要不可的信息,这些弱依赖就可以在紧急的时候去掉。

做法一般是将系统按重要程度进行分级,0级系统要尽量减少对1级系统的依赖,防止重要系统被不重要系统拖垮,在极端情况下可以把不重要系统降级,防止拖垮重要系统。

不要有单点

在系统设计中,保证高可用,我们会将每个应用部署多份,作为备份,这也是分布式系统最重要的一点。

避免单点的关键是不要将服务的状态与机器绑定,即将服务无状态化,这样服务就可以在机器中随意移动。将服务与机器状态解耦的方式:与机器相关的配置动态化,服务启动的时候从配置中心拉取,在配置中心设置一些规则来改变这些映射关系。

秒杀系统架构

  1. 秒杀系统单独打造一个系统,与普通的商品购买独立出来,可以单独的作优化
  2. 秒杀系统部署在独立机器集群,秒杀的大流量不会影响到正常的商品购买集群的负载
  3. 热点数据(如库存数据)单独放到缓存系统中,提升读性能
  4. 增加秒杀答题,防止有秒杀器抢单
  5. 页面进行动静分离,让用户秒杀使不在刷新整个界面(又重新加载所有资源),将页面刷新的数据降到最少
  6. 服务端对秒杀商品进行本地缓存,不需要再调用依赖系统的后台服务获取数据,甚至不需要去公共的缓存集群中查询数据,这样不仅可以减少系统调用,而且能够避免压跨公共缓存集群

动静分离

为了提升系统的速度,需要从两个方面去考虑:

  1. 提高单次请求的效率
  2. 减少不必要的请求

动静分离就是针对这个大方向去考量的,尽量只刷新局部数据,分离动态数据和静态数据,每次只请求动态数据,将静态数据缓存起来,客户端大幅度减少了请求的数据量。

缓存静态数据
  1. 针对不会变的静态数据,应缓存到离用户最近的地方。常见的:用户浏览器中,CDN或服务端的Cache中。
  2. 静态改造。相较于普通的数据缓存,静态化改造直接缓存HTTP连接而不是仅仅缓存数据,web服务器根据请求的URL直接取出对应的HTTP响应头和响应体直接返回,不用解析HTTP头也不同重装HTTP协议。与HTTP缓存相关的字段:
  • Pragma+Expires/Cache-Control(强制缓存):program为nocache时,客户端不会读取缓存,每次都会向服务端发请求。Expires来定义缓存的失效时间。但是expires是服务端时间,针对服务端时间与客户端不统一的情况,http1.1使用cache-control来定义缓存过期时间。优先级:Pragma>Expires>Cache-Control
  • Last-Modified/Etag(对比缓存):服务器将资源最后更改时间以Last-modified返回给客户端,客户端请求端时候将这个时间一并传给服务端做检查,如果资源没有被修改过,直接返回304,内容为空。Last-modified无法处理一秒内文件多次修改端情况,http1.1使用ETag字段,通过某种算法给资源计算出唯一标志符,客户端请求时将这个标志一起传给服务端,通过对比判断资源是否已经被修改。
  1. 在哪一层做静态缓存也很重要,不同语言的缓存处理数据的效率也不同。Java不擅长处理大量连接 请求(每个连接消耗的资源多,servelet容器解析http协议慢),所以不必在Java层做静态缓存,相比Java,Web服务器(Nginx,Apache)更擅长处理大量并发静态请求。
如何做静态化改造

分离出动态数据,以商品详情页为例子:

  1. URL唯一化: 如果要缓存整个http连接,需要以唯一的http url作为key
  2. 分离浏览者相关的因素。浏览者相关的因素包括是否登陆以及登陆身份等,这些信息可以通过动态请求获取
  3. 分离时间因素,服务器时间也通过动态请求获取(以防客户端时间和服务端时间不一致)
  4. 去掉Cookie。缓存等静态数据中不含有cookie

通过上述的原则可以分离出动态数据,这样静态数据可以通过缓存来处理,动态数据的处理通常有两种方案:

  1. ESI:在Web代理服务器上做动态内容请求,并将请求插入到静态页面中,当用户拿到页面的时候,已经是一个完整的页面离,这种方式对服务端性能有些影响
  2. CSI: 单独发起异步的JavaScript请求,向服务端获取动态内容。这种方式服务端性能更好,但用户端页面可能会延时,体验稍差

热点数据处理

热点分为热点操作和热点数据:

  • 对于秒杀系统来说,大量刷新页面,大量添加购物车,双十一零点大量下单都属于热点操作。这些操作抽象在系统层面就是读请求和写请求。
  • 热点数据又分为静态热点数据和动态热点数据:
    • 静态热点数据是能够提前预测的热点数据。比如哪些商品可能更热门,历史成交记录也可以找出来
    • 动态热点数据是不能被提前预测的,在系统运行中临时产生的热点数据,比如卖家突然做了广告,导致某个商品变得火热
发现热点数据

静态热点数据可以通过筛选,将可能热卖的商品提前进行预热处理,缓存等

动态热点数据,可以通过大数据进行预测分析发现

处理热点数据

处理热点数据的思路:

  1. 优化。优化热点数据一般是缓存热点数据
  2. 限制。限制的目的是一种保护,比如将访问商品的id做一致性hash,然后根据hash分桶,每个分桶做一个处理队列,这样就可以把热点商品限制在一个请求队列,防止某些热点商品占用太多服务器资源,而使其他商品始终得不到服务器的响应。
  3. 隔离:
    • 业务隔离:秒杀作为一种活动,参与活动的商品就是已知热点,提前做好预热
    • 系统隔离:通过分组部署,与其他普通业务隔离开来
    • 数据隔离:秒杀所调用数据大部分都是热点数据,启用单独的cache和mysql,目的也是不希望影响到其他数据

流量削峰

对于秒杀系统的流量来说,请求高度集中于某一特定时间点,这样某一个瞬间就会有一个特别高的峰值,它对资源对消耗是瞬时的。但对于秒杀这个场景来说,能抢到商品的人数是固定的,并发度越高,无效的请求越多。我们可以在真正下单的时候,设计一些规则,让并发的请求更多的延缓,甚至可以过滤到一些无效的请求。

服务器处理资源是恒定的,我们不能以峰值的要求来配置服务器,这样会让空闲的资源浪费。使用削峰的方式,错峰限行,可以让服务端处理变得更加平稳,也可以节省服务器成本。针对秒杀场景,本质上削峰是为了延缓用户请求的发出,以便减少和过滤掉一些无效请求,遵循了”请求数尽量少”的原则。

削峰一些常见的操作思路:排队、答题、分层过滤。还有一些强制措施,比如限流和机器负载保护等。

排队

对于瞬时流量,最容易想到的是通过消息队列来缓冲,把同步的直接调用转换成异步的间接推送,中间通过一个消息队列来承接瞬时的流量洪峰,在另一端将消息平滑的处理消息。

除了消息队列,类似的排队操作还有:

  1. 线程池加锁等待
  2. 先进先出等内存排队算法的实现方式
  3. 把请求序列化到文件,然后顺序读文件(例如mysql的binlog同步机制)来恢复请求
分层过滤

答题是一种限制手段,为了过滤掉一些机器请求,排队是对发出对请求进行缓冲。分层过滤的思路是用一种漏斗式的设计,来分层过滤掉一些无效请求:

  1. 大部分数据和流量在用户浏览器或CDN上获取,这一层可以拦截大部分数据的读取
  2. 第二层前台系统获取数据尽量走cache(包括强一致性的数据),这一层可以过滤一些无效的请求
  3. 第三层后台系统,主要做数据的二次校验,对系统做好保护和限流,这样数据量和请求就进一步减少
  4. 最后在数据层进行强一致性校验

就像漏洞一样,数据一层一层减少,最后到末端的数据就很少了。

分层过滤的核心思想是在不同层次尽可能的过滤掉无效请求,让达到漏洞末端的才是有效请求,这需要对数据进行分层校验

分层过滤非常适合交易性的写请求,比如减库存或者拼车这种场景,在读的时候需要知道还有没有库存或者是否还有剩余空座位。但是由于库存和座位又是不停变化的,所以读的数据是否一定要非常准确呢?其实不一定,你可以放一些请求过去,然后在真正减的时候再做强一致性保证,这样既过滤一些请求又解决了强一致性读的瓶颈。

减库存设计,防止超卖

在秒杀系统中,超卖是一个原则性问题,假如只秒杀10个商品,确有100个人抢到了,这是一个大损失。

减库存的方式

用户购物过程一般分为两步:下单和付款。在哪个环节减库存,是一个考量,一般分为几种方式:

  1. 下单减库存:即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精确的一种,下单时直接通过数据库的事务机制控制商品库存,这样一定不会出现超卖的情况。但是你要知道,有些人下完单可能并不会付款。
  2. 付款减库存:即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家。但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了。
  3. 这种方式相对复杂一些,买家下单后,库存为其保留一定的时间(如 10 分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买。在买家付款前,系统会校验该订单的库存是否还有保留:如果没有保留,则再次尝试预扣;如果库存不足(也就是预扣失败)则不允许继续付款;如果预扣成功,则完成付款并实际地减去库存。
减库存中可能存在的问题

如果使用下单减库存,很多人恶意下单后并不付款,这样可能导致恶意下单,从而影响卖家销售。

如果使用付款减库存,又可能导致超卖,因为下单成功的人数很可能超过真正的库存数,这样很多买家下单成功确无法付款,购物体验极差。

针对上述情况,将下单减库存和付款减库存两者结合起来,下单时先预扣,在规定时间内不付款在释放库存,即采用预扣库存的方式会一定程度上缓解上面的问题。

参考资料

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-06-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 秒杀系统的五个原则
    • 数据要尽量少
      • 请求数要尽量少
        • 路径要尽量短
          • 依赖要尽量少
            • 不要有单点
            • 秒杀系统架构
            • 动静分离
              • 缓存静态数据
                • 如何做静态化改造
                • 热点数据处理
                  • 发现热点数据
                    • 处理热点数据
                    • 流量削峰
                      • 排队
                        • 分层过滤
                        • 减库存设计,防止超卖
                          • 减库存的方式
                            • 减库存中可能存在的问题
                            • 参考资料
                            相关产品与服务
                            数据库
                            云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档