啥也不懂选手对于秒杀系统的思考(百度)

满纸复制文,一把辛酸泪

面试造火箭,工作拧螺丝

一、秒杀系统要解决的问题

  • 并发读:并发读的优化理念就是减少用户来服务器读数据
  • 并发写:并发写的优化理念就是减少用户来服务器写数据,数据库层面要增加新库存放数据,以便做特殊处理(比如:单独保存要秒杀商品信息,单独弄一个表存秒杀订单)
  • 在解决以上两个问题之上,还要增加兜底方案

二、秒杀系统设计的目标与原则

目标就是建立可承受超高流量并发读写,高性能,高可用系统

  • 原则就是保证用户请求数据量尽量少、请求数尽量少、路径尽量短、依赖尽量少、分布式部署保证高可用
  • 高可用:流量符合预期要稳定运行,超预期尽量能顶住,要有PlanB兜底
  • 高性能:让高并发读写快快快!!!数据动静分离、热点发现与隔离、请求削峰与分层过滤、服务端优化
  • 一致性:不能超卖、也不能少卖

三、秒杀系统架构原则

秒杀系统本质是一个大并发,高性能高可用的分布式系统,其架构原则符合4要1不要

1. 数据要尽量少

  • 请求的数据包括上传给系统的数据和系统返回给用户的数据
  • 数据传输需要耗费网络资源,花费时间。不论是请求数据还是服务器响应数据都需要服务器处理,这些处理耗费CPU资源。所以我们要简化秒杀页面的大小,去掉不必要的装饰效果
  • 系统依赖的数据能少就少,系统完成某些业务需要读取和保存的数据时,一般涉及数据的序列化和反序列化,这是耗费CPU资源的行为。另外这些数据一般会和后台服务和数据库打交道,数据库很容易称为瓶颈

2. 请求数要尽量少

  • 用户请求秒杀页面,浏览器渲染页面会包含额外的请求,比如页面所依赖的CSS、JS、图片,以及Ajax请求等。没发出一个额外请求都会有性能消耗,比如建立TCP连接,若不同请求域名不同还要涉及DNS解析。
  • 一个有效减少请求数的措施就是合并CSS和JS文件,把多个JS文件合并成一个文件,在URL中用逗号隔开,这种方式在服务端仍然单独存放,服务端会有一个组件解析这个URL,然后动态拼接这些文件合并返回。

3. 路径要尽量短

  • 减少用户发出请求到返回数据这个过程中,需求经过的中间的节点数
  • 通常,一个节点就会代表一个系统或者一个新的Socket连接(代理服务器只是创建一个新的Socket连接来转发请求)没经过一个节点,一般都会产生一个新的Socket连接
  • 每增加一个连接都会增加新的不确定性
  • 缩短请求路径可以提高可用性,同样可以提升性能(减少中间节点对数据的序列化和反序列化),减少延时
  • 缩短访问路径的一个方法是让多个强依赖的应用合并一起部署,把远程过程调用变成JVM内部之间的方法调用

4. 依赖要尽量少

  • 依赖是指,完成一次用户请求必须依赖的系统服务
  • 比如展示秒杀页面,必须强依赖展示商品信息、用户信息。但是像优惠券、成交量这些信息可以不用展示

5. 不要有单点

  • 单点意味着没有冗余,分布式系统最重要的就是消除单点
  • 避免单点关键在于避免服务状态和机器绑定,把服务无状态化,这样使得服务在机器中随意移动
  • 服务状态和机器解耦实现方式之一为把机器相关配置动态化,这些参数可以通过配置中心动态推送,在服务启动时动态拉取,我们在配置中心设置一些规则改变这些映射关系
  • 应用无状态化可以避免单点,但是对于存储服务本身很难无状态化,因为数据存储在磁盘上,本身和机器绑定,这时要通过冗余备份的方式解决单点问题

四、架构进化图

第一版 10wQPS

  • 把秒杀系统独立抽取出来
  • 系统部署时候单独建立集群,使得秒杀大流量不影响正常商品购买集群的机器负载
  • 热点数据加入缓存,提高读性能
  • 增加秒杀前验证码输入,防止抢单
  • 关联系统集群部署,比如redis、比如消息队列

在这里插入图片描述第二版 100wQPS

  • 对页面进行彻底的动静分离、使得用户秒杀时不需要刷新整个页面(js完成)、只需要点击秒杀按钮即可。把页面刷新的数据降到最少

  • 服务端对秒杀商品进行本地缓存(商品信息)、不需要调用依赖系统的后台服务获取数据,也减少对缓存集群的访问。这样既减少系统调用,也避免压垮缓存集群。

  • 增加限流防护,限制用户去秒杀接口的访问次数
    在这里插入图片描述

  • 借此得出秒杀系统瓶颈:

    • 数据库–加缓存
    • 服务端网络–前后端分离,利用浏览器缓存,静态数据放到CDN(内容分发网络)上
  • 本地缓存的实现:java集合类,HashMap足够,一般存放静态数据,系统初始化的时候把数据加载进本地缓存

  • 如果要求分布式缓存中库存量和数据库保持严格一致性,有两种维护方式:

    • 加定时任务定时刷新数据
    • 当数据字段更新后发消息更新缓存
    • 其实并不要求分布式缓存中库存和数据库中严格一致
  • 注意设置缓存的内存淘汰策略,防止无效数据占据大量内存

  • 服务器硬件性能(CPU、内存、磁盘IO速度、网卡)

六、什么是动静数据

只有分离了动静数据,才能对静态数据做缓存,才能提高对静态数据的访问效率

  • 动态数据和静态数据的主要区别是页面中输出的数据是否和URL、浏览者、时间、地域相关、以及是否含有cookie等私密信息
  • 比如一篇文章不管谁访问,都是一样的,这就是一种典型的静态数据,但是他是一个动态页面。
  • 淘宝首页,每个人看到的信息不同,包含了根据个人特征的推荐信息,这些个性化的数据就叫动态数据
  • 静态数据不能仅仅狭隘的理解为传统意义上完全存在于磁盘上的HTML数据。所谓动态还是静态是指数据中是否含有和访问者相关的个性化数据。页面中不包含是指页面HTML源码中不含有

七、静态信息缓存在何处

  • 把静态数据缓存在离用户最近的地方,因为静态数据相对不会变化,因此可以缓存起来
  • 主要缓存在用户浏览器、CDN上、服务端缓存中
  • 静态化改造,静态化改造不仅仅缓存数据还直接缓存HTTP连接。如图,Web代理服务器根据请求URL,直接取出对应HTTP响应头和响应题直接返回,这个响应过程连HTTP协议也不用重新组装,HTTP请求头也不用解析
    在这里插入图片描述

八、动静分离改造

以商品详情页为例,从五个方面分离动态数据

  • URL唯一化,商品详情系统天然地就可以做到URL唯一化,比如每个商品都由ID标识,那么URL http://item.xxx.com/item.htm?id=xxxx 就可以作为唯一的URL标识。唯一的URL作用就是在缓存整个HTTP连接时作为key
  • 分离浏览者相关的因素,浏览者相关因素包括是否已经登录,以及登录身份等,这些相关因素我们可以单独拆分出来,通过动态请求获取
  • 分离时间因素,服务端输出的时间也通过动态请求获取
  • 异步化地域因素、详情页面上与地域相关的因素做成异步方式获取,当然也可以动态请求获取
  • 去掉Cookie。服务端输出的页面包含的Cookie可以通过代码软件删除,比如Web服务器Varnish可以通过unset req.http.cookie命令去掉Cookie。注意,这里的去掉Cookie不是用户端收到的页面不含cookie而是缓存的静态数据中不含cookie

注意: 这些动态数据会被页面中的其他模块用到,如判断是否登录,我们要将这些信息Json化,以便前端获取

动态数据的处理方案

  • ESI(Edge Side Includes):在Web代理服务器上做动态内容请求,并将请求插入到静态页面中,当用户拿到页面时已经是一个完整的页面了、这种方式对服务端性能有些影响,但是用户体验较好
  • CSI(Client Side Include):单独发起一个异步JavaScript请求,以向服务器端获取动态内容。这种方式服务端性能更佳,但是用户页面延时,体验稍差

九、动静分离的几种架构方案

前面已经通过改造把静态数据和动态数据做了分离,如何在系统架构上进一步对动静数据进行重新组合?这就涉及对用户请求路径进行合理的架构了,有三种方式可选

  • 实体机单机部署
  • 同一Cache层
  • 上CDN

注意下边Cache层是指:web缓存可以直接缓存Http请求如varnish

方案一、实体机单机部署

  • 这种方案是将虚拟机改为实体机,以增大Cache的容量,并且采用了一致性Hash分组的方式来提高命中率。这里将Cache分成若干组,是希望能达到命中率和访问热点的平衡。Hash分组越少,缓存命中率肯定就会越高,但短板是也会使单个商品集中在一个分组中,容易导致缓存击穿,所以我们应该适当增加多个相同的分组,平衡访问热点和命中率的问题
    -

好处:

  • 没有网络瓶颈、能使用大内存
  • 提升命中率、减少Gzip压缩
  • 减少Cache失效压力,采用定时失效方式、例如只缓存3s,过期自动失效

缺点:

  • 虽然通常只需要把虚拟机或者容器运行的Java应用换成实体机,优势很明显,他会增加单机的内存容量,但是一定程度造成了CPU的浪费,因为单个Java进程很难用完整个实体机CPU。
  • 实体机上部署了Java应用,又作为Cache使用,造成了运维上的高度复杂,所以这是折中方案。如果有少量需求需要静态化,这么做可以,如果有较多静态化改造的需求,推荐把缓存层单独抽取出来公用比较合理,如下

方案二、统一Cache层
所谓统一Cache层,就是将单机的Cache统一分离出来,形成一个单独的Cache集群。统一Cache层是一个更理想的可推广方案。
在这里插入图片描述

好处:

  • 将Cache层单独拿出来统一管理可以减少运维成本,同时也方便接入其他静态化系统
  • 单独一个Cache层,可以减少多个应用接入时使用Cache的成本。这样接入的应用只要维护自己的Java系统就好,不需要单独维护Cache,只关心如何使用即可
  • 统一的Cache方案更易于维护
  • 可以共享内存,最大化利用内存,不同系统之间的内存可以动态切换,从而能够有效应对各种攻击

缺点:

  • 缓存更加集中
  • Cache层内部交换网络称为瓶颈
  • 缓存服务器的网卡也是瓶颈
  • 机器少风险较大,挂掉一台就会影响一大部分缓存数据
  • 要解决上面的问题,可以再对Cache做Hash分组,即一组Cache缓存的内容相同,这样能够避免热点数据的过度集中问题。

方案三、上CDN
在将整个系统做动静分离后,我们自然会想到更近一步的方案,就是将Cache进一步前移到CDN上,因为CDN离用户更近,但有以下问题需要解决

  • 失效问题:我们需要保证CDN可以在秒级时间内,让分布在全国各地的Cache同时失效,这对于CDN的失效系统要求很高。不然用户可能会看到错误数据
  • 命中率问题:Cache最重要的一个衡量指标就是高命中率,不然Cache的存在就失去意义。同样如果将数据全部放到全国的CDN上,必然导致Cache分散,Cache分散就会导致访问请求命中率低
  • 发布更新问题:如果一个业务每周都会更新,那么发布系统必须简洁高效,要考虑出现问题回滚和排查

综上所述,把商品详情页面放在全国的CDN节点上不太现实,因为存在失效问题,命中率问题和发布更新问题,但是可以选择若干节点尝试,这些节点满足如下条件

  • 靠近访问量比较集中的地区
  • 离主站相对较远
  • 节点到主站点的网络稳定
  • 节点容量大,不会占用其他CDN太多资源
  • 节点不要太多

基于此选择CDN的二级Cache比较合适,因为二级Cache数量偏少,容量也大,让用户请求先找CDN二级缓存,若没有命中在回源站获取数据

在这里插入图片描述

  • 使用CDN二级Cache作为缓存,可以达到和当前服务端静态化Cache类似的命中率,因为节点不多,Cache不是很分散,访问量也比较集中,这样也就解决了命中率的问题,同时能给用户带来最好的访问体验,是当前比较理想的一种CDN方案
  • CDN化部署也会把整个页面缓存在用户浏览器中
  • 如果强制刷新页面,也会请求CDN
  • 实际有效请求,只是用户对秒杀按钮的点击

这样就把90%的静态数据缓存在了用户端或者CDN上,当真正秒杀时,用户只需要点击特殊的秒杀按钮,不需要刷新整个页面。这样一来,系统只向服务端请求很少的有效数据,无需重复请求大量的静态数据。

总结一波

  • 缓存存在浏览器的缺点:存在CDN上我们可以做主动失效,而在浏览器上我们不可控,如果用户不主动刷新的话,很难主动把消息推送给用户浏览器
  • 在什么地方把静态数据和动态数据合并渲染出一个完整的页面很关键,假如在浏览器合并,服务端可以减少渲染整个页面的CPU开销,如果服务端合并的话,需要考虑缓存的数据是否进行了Gzip压缩,如果缓存了Gzip压缩后的静态数据可以减少缓存的数据量,但是进行页面合并渲染时就要先解压,然后在压缩完整页面数据传输给用户。
  • 推荐秒杀在客户端做

十、热点数据的发现和处理

  • 热点操作:秒杀下单,秒杀详情页的访问,此操作会涉及读操作和写操作
  • 热点数据:秒杀商品信息,用户频繁请求的数据等

如何发现热点商品

  • 秒杀商品就是热点商品,关键是如何发现他们是要秒杀的商品
  • 强制卖家报名参加活动时填写秒杀商品目录,把热点商品筛选出来,对商品信息提前进行缓存,这样会降低卖家的体验
  • 对买家每天对于商品的访问量统计TOP K,认为这K个商品就是热点商品
  • 设计一个动态发现热点商品的系统:
    • 构建一个 异步系统,收集交易链路上各个环节的中间件产品热点key,如Ngnix、缓存、RPC框架等
    • 建立热点上报和热点数据下发的规范,目的是通过交易链路上各个系统访问时间差,把上游已经发现的热点数据传给下游,以便提前做好防护

在这里插入图片描述

  • 用户访问商品时经过的路径有很多,我们主要是依赖前面的导购页面(包括首页、搜索页面、商品详情、购物车等)提前识别哪些商品的访问量高,通过这些系统中的中间件来收集热点数据,并记录到日志中。
  • 我们通过部署在每台机器上的 Agent 把日志汇总到聚合和分析集群中,然后把符合一定规则的热点数据,通过订阅分发系统再推送到相应的系统中。你可以是把热点数据填充到 Cache 中,或者直接推送到应用服务器的内存中,还可以对这些数据进行拦截,总之下游系统可以订阅这些数据,然后根据自己的需求决定如何处理这些数据。

热点发现系统的注意事项

  • 这个热点服务后台抓取热点数据日志最好采用异步方式,因为“异步”一方面便于保证通用性,另一方面又不影响业务系统和中间件产品的主流程。
  • 热点服务发现和中间件自身的热点保护模块并存,每个中间件和应用还需要保护自己。热点服务台提供热点数据的收集和订阅服务,便于把各个系统的热点数据透明出来。
  • 热点发现要做到接近实时(3s 内完成热点数据的发现),因为只有做到接近实时,动态发现才有意义,才能实时地对下游系统提供保护。

如何处理热点数据

  • 一是优化,二是限制,三是隔离。

  • 优化热点数据最有效的办法就是缓存热点数据,如果热点数据做了动静分离,那么可以长期缓存静态数据。但是,缓存热点数据更多的是“临时”缓存,即不管是静态数据还是动态数据,都用一个队列短暂地缓存数秒钟,由于队列长度有限,可以采用 LRU 淘汰算法替换。

  • 限制更多的是一种保护机制,限制的办法也有很多,例如对被访问商品的 ID 做一致性 Hash,然后根据 Hash 做分桶,每个分桶设置一个处理队列,这样可以把热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源,而使其他请求始终得不到服务器的处理资源。

  • 把热点数据隔离出来,不要让 1% 的请求影响到另外的 99%,隔离出来后也更方便对这 1% 的请求做针对性的优化。

    • 业务隔离。把秒杀做成一种营销活动,卖家要参加秒杀这种营销活动需要单独报名,从技术上来说,卖家报名后对我们来说就有了已知热点,因此可以提前做好预热。
    • 系统隔离。系统隔离更多的是运行时的隔离,可以通过分组部署的方式和另外 99% 分开。秒杀可以申请单独的域名,目的也是让请求落到不同的集群中。
    • 数据隔离。秒杀所调用的数据大部分都是热点数据,比如会启用单独的 Cache 集群或者 MySQL 数据库来放热点数据,目的也是不想 0.01% 的数据有机会影响 99.99% 数据。
  • 实现隔离有很多种办法。比如,你可以按照用户来区分,给不同的用户分配不同的 Cookie,在接入层,路由到不同的服务接口中;再比如,你还可以在接入层针对 URL 中的不同 Path 来设置限流策略。服务层调用不同的服务接口,以及数据层通过给数据打标来区分(就是加上一个标识区分出是否是热点数据)等等这些措施,其目的都是把已经识别出来的热点请求和普通的请求区分开

十一、为啥要削峰

  • 对于秒杀这个场景来说,最终能抢到商品的人数是固定的,也就是说100和10000个人参与秒杀请求结果都是固定人数秒杀成功,并发度越高无效请求也越高
  • 但是从业务层面希望更过人参与秒杀,所以我们必须设计规则,让并发的请求延缓,甚至过滤无效请求
  • 服务器的处理资源是固定的,如果出现请求峰值,很容易导致忙不过来,闲的时候却又没什么处理。如果要保证服务质量按照极端忙碌的情况设置服务器资源大小,很大程度会在空闲时浪费掉。
  • 削峰的存在,一是让服务器处理变得平稳,二是可以节约服务器成本。针对秒杀这一场景,削峰的本质就是更多地延缓用户请求的发出,以便减少和过滤一些无效的请求,它遵循我们说的请求数尽量少原则

十二、怎么才能削峰?

  • 这里先介绍无损的操作思路,即不损失用户发出请求的方案
  • 排队、答题、分层过滤
  • 注意、有损的方式有限流和机器负载保护等这里先不提

排队

  • 利用消息队列缓冲瞬时流量,把同步的直接调用转换为异步的间接推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平稳的进行消息推送
  • 另外注意,如果流量峰值持续一段时间到达了消息队列的处理上限,消息队列也会被压垮
    在这里插入图片描述

除了消息队列,排队的方式还可如下

  • 利用线程池加锁等待
  • 先进先出、先进后出等内存排队算法的实现
  • 把请求序列化到文件,然后顺序读取文件来恢复请求

答题

  • 首先增加秒杀前题目答案的输入,可以有效防止秒杀器参与作弊
  • 其次可以延缓请求,起到对流量进行削峰的作用。请求峰值基于时间分片,大大减轻服务器压力,由于请求存在先后顺序,靠后的请求就不会走到下单步骤,也就不存在并发写数据库的情况。

在这里插入图片描述

  • 题库生成模块:生成问题和答案,问题不用很复杂能防止秒杀器答题即可
  • 题库推送模块,用于秒杀答题前,把题目推送给详情系统和交易系统。题库的推送主要是为了保证每次用户请求的题目唯一,为了方式秒杀器答题
  • 题目图片生成模块,用于把题目生成为图片格式,并在图片中加干扰。由于答题时网络比较拥挤,我们应该把图片提前推送到CDN上预热,不然可能图片加载过慢。

验证结果逻辑如下
在这里插入图片描述

  • 验证时除了结果验证还有用户身份验证,Cookie验证,是否频繁提交请求等
  • 另外还可以对提交答案时间做限制,如果答题时间小于1s说明不正常。

分层过滤

  • 排队是对请求进行缓冲,答题是分散用户请求,而分层过滤就是过滤掉无用的请求。分层过滤其实就是采用漏斗式的设计处理请求
    在这里插入图片描述

假如请求经过CDN、前台读系统(商品详情系统)、后台系统(交易系统)和数据库这几层:

  • 大部分数据和流量在用户浏览器或者CDN上获取,这一层可以拦截大部分数据的获取
  • 经过第二层(商品详情系统)时数据(包括强一致性的数据)尽量走Cache,过滤一些无效的请求
  • 再到第三层后台系统,主要做数据的二次检验,对系统做好保护和限流,这样数据量和请求会进一步减少
  • 最后在数据层完成强一致性校验

分层过滤的核心思想就是在不同层次尽可能过滤掉无效请求,让漏斗最末端的才是有效请求,对此我们要做分层校验,分层校验的基本原则如下:

  • 将动态请求的读数据缓存(Cache)在web端,过滤掉无效的数据读
  • 对读数据不做强一致性校验,减少因为一致性校验的瓶颈
  • 对写数据进行基于时间的合理分片,过滤掉过期的失效请求
  • 对写请求做限流保护,将超出系统承载能力的请求过滤掉
  • 对写数据进行强一致性校验,只保留最后有效数据

分层校验的目的

  • 读系统中,尽量减少由于一致性校验带来的系统瓶颈,尽量将不影响性能的检查条件提前,如用户是否有秒杀资格,商品状态是否正常,用户答题是否正确,秒杀是否结束,是否是非法请求
  • 写系统中主要是对写的(“库存”)做一致性检查,最后在数据库层保证数据的最终准确性

十三、影响秒杀系统性能的因素有哪些?

  • 先说结论主要是CPU资源的瓶颈(当然还有网络、数据库等)
  • 这里主要讨论影响服务端性能的因素,主要衡量的指标为QPS(每秒请求数)和RT(响应时间),响应时间也可理解为服务器处理响应的耗时。
  • 正常情况下响应时间越短,一秒钟处理能处理的请求数(QPS)越多,总QPS = (1000ms/响应时间)*线程数量。
  • 而响应时间一般由CPU执行时间和线程等待时间(RPC、IO等待、Sleep、Wait等)组成。
  • 单纯减少线程等待时间并不能提升太多的性能,因为系统可承受的吞吐量没有变,减少等待时间并没有减少对系统资源的消耗。所以真正对性能影响的是CPU的执行时间,这很好理解因为CPU执行耗费了服务器资源
  • 另一方面从总QPS公式中可以看到,线程数量增大也会增加QPS。但是这不是绝对的,因为线程本身也会耗费资源,线程切换的成本也很高。一般经验公式线程数为2*CPU核数+1。当然最好还是用性能测试去判断。

所以对于秒杀系统来说这个问题转变为哪里消耗CPU资源哪里就可能是瓶颈,判断CPU是不是瓶颈的方法为当QPS达到极限,服务器CPU是否超过95%,如果没有说明可以优化,比如锁限制,或者有过多的IO等待。

如何优化系统

主要分为减少编码,减少序列化,Java极致优化,并发读优化。

1.减少编码

  • Java的编码运行比较慢,只要涉及字符串的操作(输入输出操作、I/O操作)都比较耗费CPU资源,因为不管磁盘IO还是网络IO都涉及字符转换为字节,这个过程涉及查表很耗费资源
  • 网页输出是可以直接进行流输出的,resp.getOutputStream()函数写数据,把一些静态资源数据提前转化为字节,等写数据的时候直接用OutputStream函数写,减少静态数据的编码转换

2.减少序列化

  • 序列化是Java性能天敌,减少Java中的序列化操作也能大大提升性能,序列化过程往往和编码同时进行,减少序列化也就减少了编码
  • 序列化发生在RPC中,可以将多个关联性很强的应用进行合并部署,减少不同应用之间的RPC也可以减少序列化的消耗,所谓联合部署是不仅仅部署到一台机器上,还要同一个tomcat容器中,不能走本机socket才能避免序列化

3.Java极致优化

  • Java和通用的web服务器(Ngnix和Apache服务器)相比,处理大并发的HTTP请求弱一点,一般对大流量的Web系统做静态化改造,让大部分请求直接在Ngnix服务器或Web代理服务器(Varnish等)上返回,这样也可以减少序列化和反序列化,Java层面只需要处理少量的动态请求。
  • 对于动态请求,直接使用Servlet处理,避免使用传统MVC框架绕一大圈处理逻辑,另外直接输出流数据。使用resp.getOutputStream()而不是resp.getWriter()函数,可以省掉一些不变字符数据的编码;数据输出推荐使用JSON而不是模板引擎,模板引擎多为解释执行。

4.并发读优化

  • 一般思路是加入缓存,集中式缓存为了保证命中率一般会采用一致性Hash,同一个key会落到同一台机器上。一般单台缓存机器能支撑30w/s的请求,但还是远不足以应对像特别热点的商品。
  • 为了解决这个问题可以加入本地缓存,在秒杀系统单机上缓存商品相关的数据。
  • 对于商品标题和描述等静态数据在秒杀之前全量推送到秒杀机器上,一直缓存到活动结束
  • 库存这类动态数据,可以在保存一定时间后去拉取最新数据。另外对于这个库存的频繁修改不必担心数据一致性带来的超卖问题,因为这里若发生误判等真正写数据的时候会最终保证一致性。

十四、一般减库存的处理方式

  • 下单减库存:当买家下单后,在商品的总库存中减去买家购买数量。下单减库存时最简单的减库存方式,也是控制最精确的一种,下单时直接通过数据库的事务机制控制商品库存,这样一定不会发生超卖的情况,但是问题在于有些人下完单不会付款。
  • 付款减库存,买家下单后,并不立即减库存,而是等到用户付款时才真正的减库存,否则库存一直保留给其他买家。因为付款时才减库存,如果并发度很大,可能出现买家下完单减不了库存的情况。
  • 预扣库存,买家下单后,库存为其保留一段时间,超时未付款,库存自动释放,后续买家可以继续购买。买家付款前会在此校验是否库存还保留;如果没有保留尝试预扣,库存不足提示失败。

存在的问题

  • 下订单减库存的方式如果买家没有付款会导致商家商品卖不出去,甚至存在竞争对手恶意刷单
  • 付款减库存的方式会存在用户下单成功但是因为没有库存而付款失败,如果处理不当甚至会出现超卖现象
  • 预扣库存只能缓解下订单减库存造成的恶意刷单现象,因为若想真的恶意刷单也能做到等订单超时重新下单不付款,针对这种情况解决的方法是结合安全和反作弊措施来制止。比如给经常下单不付款的用户做标记,下次他下单时可以不预减库存、设置活动商品最大购买数,限制重复下单不付款的次数。

十五、针对秒杀场景的减库存的方式

  • 参加秒杀下单后不付款的极少,所以采用的是下单减库存的方式
  • 下单减库存在数据一致性上主要就是保证大并发请求时库存数据不能为负数
  • 一种是在应用程序中通过事务来判断,保证减后库存不能为负数,否则就回滚
  • 或者直接设置库存类型为无符号整数,当减库存字段小于0直接报错回滚
  • 或者在SQL语句中加上count>0的条件

注意:防止超卖是重中之重,前面设置的各种缓存都是为了拦截请求并不能做到和数据库中库存保持一致性。

  • MySQL存储数据,写数据时会有大量线程竞争InnoDB行锁,并发读越高等待线程越高,数据库处理的响应时间越大,数据库的吞吐量会受到严重影响,这样单个热点商品会影响整个数据库性能。

  • 但是好在使用了消息队列进行排队处理减少了同一时刻并发量

  • 一个大胆的想法,Redis中放两个库存,一个加锁一个不加锁,不加锁的用来拦截请求,加锁的用来保证不超卖不知是否会比在MySQL数据库中减库存要快一点呢?

十六、高可用系统的建设思路

在这里插入图片描述

  • 架构阶段:架构阶段主要考虑系统可扩展性和容错性,避免系统出现的单点问题。
  • 编码阶段:编码最重要的是保证代码的健壮性。
  • 测试阶段:考虑测试用例的覆盖面
  • 发布阶段:要设置紧急回滚机制
  • 运行阶段:监控系统,出现问题及时报警
  • 故障发生:故障发生要能及时止损

十七、秒杀系统高可用的建立思路

降级、限流、拒绝服务

降级

  • 降级就是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,把有限的资源分配给核心的业务
  • 比如当秒杀流量达到5w/s时,把成交记录的获取从展示20条降级到只展示5条。这个改动过程由一个开关实现,也就是设置一个能够从开关系统动态获取的系统参数

在这里插入图片描述
限流

  • 限流的实现方式既要支持URL以及方法级别的限流,也要支持基于QPS和线程的限流
  • 客户端限流,好处是限制请求的发出,通过减少发出无用请求从而减少对系统的消耗。缺点是当客户端分散时,没法设置合理的限流阈值,如果阈值设置太小,会导致服务端没有达到性能瓶颈时客户端已经被限制,如果限制太大可能起不到限制的作用
  • 服务端限流,好处是可以根据服务端性能设置合理的阈值,缺点是被限制的请求都是无效的请求,处理这些无效的请求本身也会消耗服务器资源

在这里插入图片描述

  • 在限流的手段上,基于QPS和线程数的限流应用最多,最大QPS可以通过压测获取

拒绝服务

  • 如果限流还不能解决问题,最后一招就是拒绝服务
  • 当系统负载达到阈值,例如CPU使用率达到90%,系统直接拒绝所有服务
  • 在最前端的Ngnix上设置过载保护,当机器达到某个值直接拒绝HTTP请求返回503错误码

相关资料

如何设计秒杀系统

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页