高并发系统-设计秒杀系统关注点

秒杀系统是一个比较典型的短时高流量的应用场景,且大部分人争抢的部分有限资源

1. 架构原则

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

1.1 数据尽量少

因为数据在网络传输需要时间,其次请求数据和返回数据都需要服务端做处理(写网络的压缩和字符编码,rpc调用的序列化与反序列化)

依赖的系统和组件越少越好

1.2 请求数越少越好

1.3 路径尽量短

1.4 请求依赖越短越好

1.5 不要有单点

越追求极致性能,系统定制开发就会越多,同时系统的通用性也就会越差。

2. 架构案例

以淘宝早期案例为例:

2.1 早期简化方案

如果你想快速搭建一个简单的秒杀系统,只需要把你的商品购买页面增加一个"定时上架"功能,仅在秒杀开始时才让用户看到购买按钮,当商品的库存卖完了也就结束了。

2.2 优化一:支撑10w/s

  1. 把秒杀系统独立出来单独打造一个系统,这样可以有针对性地做优化,例如这个独立出来的系统就减少了店铺装修的功能,减少了页面的复杂度;
  2. 在系统部署上也独立做一个机器集群,这样秒杀的大流量就不会影响到正常的商品购买集群的机器负载;
  3. 将热点数据(如库存数据)单独放到一个缓存系统中,以提高"读性能";
  4. 增加秒杀答题,防止有秒杀器抢单。

2.3 优化二:超过100W/s

  1. 对页面进行彻底的动静分离,使得用户秒杀时不需要刷新整个页面,而只需要点击抢宝按钮,借此把页面刷新的数据降到最少;
  2. 在服务端对秒杀商品进行本地缓存,不需要再调用依赖系统的后台服务获取数据,甚至不需要去公共的缓存集群中查询数据,这样不仅可以减少系统调用,而且能够避免压垮公共缓存集群。
  3. 增加系统限流保护,防止最坏情况发生。

3. 关键设计点

3.1 动静分离

"动态数据"和"静态数据"的主要区别就是看页面中输出的数据是否和URL、浏览者、时间、地域相关,以及是否含有Cookie等私密数据

3.1.1 如何做动静分离

  • URL唯一化
  • 分离浏览者相关的因素。浏览者相关的因素包括是否已登录,以及登录身份等,这些相关因素我们可以单独拆分出来,通过动态请求来获取。
  • 分离时间因素。服务端输出的时间也通过动态请求获取。
  • 异步化地域因素。详情页面上与地域相关的因素做成异步方式获取
  • 去掉Cookie。服务端输出的页面包含的Cookie可以通过代码软件来删除

3.1.2 动态数据处理方案

ESI(Edge Side Includes)方案和CSI(Client Side Include)方案。

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

3.1.3 集中架构方案

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

3.2 处理热点数据

3.2.1 什么是热点数据

热点请求会大量占用服务器处理资源,虽然这个热点可能只占请求总量的亿分之一,然而却可能抢占90%的服务器资源,如果这个热点请求还是没有价值的无效请求,那么对系统资源来说完全是浪费。

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

  • 热点操作:例如大量的刷新页面、大量的添加购物车、双十一零点大量的下单等都属于此类操作。优化的思路就是根据CAP理论做平衡
  • 热点数据:用户的热点请求对应的数据
    • 静态热点数据:能够提前预测的热点数据。例如,我们可以通过卖家报名 的方式提前筛选出来,通过报名系统对这些热点商品进行打标。另外,我们还可以通过大数据分析来提前发现热点商品,比如我们分析历史成交记录、用户的购物车记录,来发现哪些商品可能更热门、更好卖,这些都是可以提前分析出来的热点。
    • 动态热点数据:系统在运行过程中临时产生的热点。例如,卖家在抖音上做了广告,然后商品一下就火了,导致它在短时间内被大量购买。

3.2.2 如何发现热点数据

  1. 发现静态热点数据
    • 提前报名筛选
    • 技术手段提前预测:对买家每天访问的商品进行大数据计算,然后统计出TOP N的商品
  2. 发现动态热点数据 构建一个动态发现热点数据系统:
    • 构建一个异步的系统,它可以收集交易链路上各个环节中的中间件产品的热点Key,如Nginx、缓存、RPC服务框架等这些中间件(一些中间件产品本身已经有热点统计模块)。
    • 建立一个热点上报和可以按照需求订阅的热点服务的下发规范,主要目的是通过交易链路上各个系统(包括详情、购物车、交易、优惠、库存、物流等)访问的时间差,把上游已经发现的热点透传给下游系统,提前做好保护。比如,对于大促高峰期,详情系统是最早知道的,在统一接入层上Nginx模块统计的热点URL。
    • 将上游系统收集的热点数据发送到热点服务台,然后下游系统(如交易系统)就会知道哪些商品会被频繁调用,然后做热点保护。

3.2.3 处理热点数据

  • 优化:缓存热点数据
  • 限制:被访问商品的ID做一致性Hash,然后根据Hash做分桶,每个分桶设置一个处理队列,这样可以把热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源
  • 隔离:不要让1%的请求影响到另外的99%,隔离出来后也更方便对这1%的请求做针对性的优化
    • 业务隔离:做成单独营销活动,提前识别热点数据
    • 系统隔离:运行时的隔离,可以通过分组部署的方式和另外99%分开
    • 数据隔离:启用单独的cache集群和mysql数据库

3.3 流量削峰

服务器处理资源是恒定的,出现峰值的时候很容易忙导致处理不过来

3.3.1 排队

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

3.3.2 答题

止部分买家使用秒杀器在参加秒杀时作弊

延缓请求,起到对请求流量进行削峰的作用,从而让系统能够更好地支持瞬时的流量高峰

3.3.3 分层过滤

对请求进行分层过滤,从而过滤掉一些无效的请求。分层过滤其实就是采用"漏斗"式设计来处理请求的

分层过滤的核心思想是:在不同的层次尽可能地过滤掉无效请求,让"漏斗"最末端的才是有效请求。而要达到这种效果,我们就必须对数据做分层的校验。

分层校验的基本原则是:

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

3.4 提高系统性能

3.4.1 影响性能因素

系统服务端性能:QPS(Query Per Second,每秒请求数)和响应时间(Response Time,RT)

响应时间和QPS 的关系:响应时间一般都是由CPU执行时间和线程等待时间(比如RPC、IO等待、Sleep、Wait等)组成,即服务器在处理一个请求时,一部分是CPU本身在做运算,还有一部分是在各种等待。

降低RT需要减少CPU执行时间

线程数对QPS的影响:要提升性能我们就要减少CPU的执行时间,另外就是要设置一个合理的并发线程数,通过这两方面来显著提升服务器的性能。

线程数 = [(线程等待时间 + 线程CPU时间) / 线程CPU时间] × CPU数量

3.4.2 如何发现瓶颈

可使用arthes火焰图统计CPU耗时

APM监控工具查看

3.4.3 优化系统

  • 减少编码
  • 减少序列化
  • Java极致优化
  • 并发读优化:本地缓存

性能优化的过程首先要从发现短板开始,还可以在减少数据、数据分级(动静分离),以及减少中间环节、增加预处理等这些环节上做优化。
要做好优化,你还需要做好应用基线,比如性能基线(何时性能突然下降)、成本基线(去年双11用了多少台机器)、链路基线(我们的系统发生了哪些变化),你可以通过这些基线持续关注系统的性能,做到在代码上提升编码质量,在业务上改掉不合理的调用,在架构和调用链路上不断的改进。

3.5 扣库存设计逻辑

3.5.1 扣库存方式

  • 下单扣库存:导致下单不付款
  • 付款扣库存:导致下单没法付款或者付款后没库存取消订单
  • 预扣库存:下单后保留一段时间库存

3.5.2 优化

"库存"是个关键数据,也是个热点数据,因为交易的各个环节中都可能涉及对库存的查询。但是,我在前面介绍分层过滤时提到过,秒杀中并不需要对库存有精确的一致性读,把库存数据放到缓存(Cache)中,可以大大提升读性能。

并发写优化:可以将扣库存操作放到缓存系统(REDIS)中,异步记录到MYSQL数据库 同时基于秒杀商品做排队机制,避免占用过多资源

3. 兜底方案设计

3.1 高可用服务

  1. 架构阶段:架构阶段主要考虑系统的可扩展性和容错性,要避免系统出现单点问题。例如多机房单元化部署,即使某个城市的某个机房出现整体故障,仍然不会影响整体网站的运转。
  2. 编码阶段:编码最重要的是保证代码的健壮性,例如涉及远程调用问题时,要设置合理的超时退出机制,防止被其他系统拖垮,也要对调用的返回结果集有预期,防止返回的结果超出程序处理范围,最常见的做法就是对错误异常进行捕获,对无法预料的错误要有默认处理结果。
  3. 测试阶段:测试主要是保证测试用例的覆盖度,保证最坏情况发生时,我们也有相应的处理流程。
  4. 发布阶段:发布时也有一些地方需要注意,因为发布时最容易出现错误,因此要有紧急的回滚机制。
  5. 运行阶段:运行时是系统的常态,系统大部分时间都会处于运行态,运行态最重要的是对系统的监控要准确及时,发现问题能够准确报警并且报警数据要准确详细,以便于排查问题。
  6. 故障发生:故障发生时首先最重要的就是及时止损,例如由于程序问题导致商品价格错误,那就要及时下架商品或者关闭购买链接,防止造成重大资产损失。然后就是要能够及时恢复服务,并定位原因解决问题。

3.2 兜底方案

3.2.1 降级

所谓"降级",就是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。

3.2.2 限流

如果说降级是牺牲了一部分次要的功能和用户的体验效果,那么限流就是更极端的一种保护措施了。限流就是当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。 分为客户端限流、服务端限流

3.2.3 拒绝服务

当系统负载达到一定阈值时,例如CPU使用率达到90%或者系统load值达到2*CPU核数时,系统直接拒绝所有请求,这种方式是最暴力但也最有效的系统保护方式。

参考:
秒杀系统架构设计都有哪些关键点?

相关推荐
PAK向日葵4 小时前
【算法导论】PDD 0817笔试题题解
算法·面试
uzong5 小时前
技术故障复盘模版
后端
GetcharZp6 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程6 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研6 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi6 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国8 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy8 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
喂完待续8 小时前
Apache Hudi:数据湖的实时革命
大数据·数据仓库·分布式·架构·apache·数据库架构
AntBlack8 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt