秒杀的真面目
秒杀系统的注意的五大架构原则
4要1不要
1.数据要尽量少
- 首先是指用户请求的数据能少就少。因为首先这些数据在网络上传输需要时间,其次不管是请求数据还是返回数据都需要服务器做处理,而服务器在写网络时通常都要做压缩和字符编码,这些都非常消耗 CPU,所以减少传输的数据量可以显著减少 CPU 的使用。
2.请求数要尽量少
- 用户请求的页面返回后,浏览器渲染这个页面还要包含其他的额外请求,比如说,这个页面依赖的 CSS/JavaScript、图片,以及 Ajax 请求等等都定义为“额外请求”,这些额外请求应该尽量少。 例如,减少请求数最常用的一个实践就是合并 CSS 和 JavaScript 文件,把多个 JavaScript
文件合并成一个文件,在 URL 中用逗号隔开(https://g.xxx.com/tm/xxb/4.0.94/mods/??module-preview/index.xtpl.js,module-jhs/index.xtpl.js,modulefocus/index.xtpl.js)。这种方式在服务端仍然是单个文件各自存放,只是服务端会有一个组件解析这个 URL,然后动态把这些文件合并起来一起返回。3.路径要尽量短
- 所谓“路径”,就是用户发出请求到返回数据这个过程中,需求经过的中间的节点数。
4.依赖要尽量少
- 所谓依赖,指的是要完成一次用户请求必须依赖的系统或者服务,这里的依赖指的是强依赖。
5.不要有单点
- 系统中的单点可以说是系统架构上的一个大忌,因为单点意味着没有备份,风险不可控,我们设计分布式系统最重要的原则就是“消除单点”。应用无状态化是有效避免单点的一种方式,但是像存储服务本身很难无状态化,因为数据要存储在磁盘上,本身就要和机器绑定,那么这种场景一般要通过冗余多个备份的方式来解决单点问题。
不同场景下面的架构
简单的秒杀系统
- 在用户页面提供一个秒杀按钮,到时间了就可以进行秒杀。
1w/s-10w/s量级QPS
- 将秒杀系统独立出来,并做成集群,将热点数据放在缓存中,增加秒杀答题防止秒杀机器抢单。
100w/s的量级的QPS
- 对页面进行彻底的动静分离,使得用户秒杀时候不用刷新整个页面,客户端对秒杀商品进行缓存,增加系统限流保护。
具体的场景具体分析
- 秒杀的场景来说,不同QPS量级下瓶颈也会不一样,10w级别可能瓶颈就在数据读取上,通过增加缓存一般就能解决,如果要到100w那么,可能服务端的网络可能都是瓶颈,所以要把大部分的静态数据放到cdn上甚至缓存在浏览器里
优化的具体方案-如何做好动静分离
将静态数据缓存到离用户最近的地方
- 1,浏览器、2,CDN、3,服务器端的Cache。
静态化改造就是要直接缓存 HTTP 连接
- 静态化改造是直接缓存 HTTP 连接而不是仅仅缓存数据,Web 代理服务器根据请求 URL,直接取出对应的 HTTP 响应头和响应体然后直接返回,这个响应过程简单得连 HTTP 协议都不用重新组装,甚至连 HTTP 请求头也不需要解析。
让谁来缓存静态数据也很重要
- 不同语言写的 Cache 软件处理缓存数据的效率也各不相同。以 Java 为例,因为 Java 系统本身也有其弱点(比如不擅长处理大量连接请求,每个连接消耗的内存较多,Servlet 容器解析 HTTP 协议慢),所以你可以不在 Java 层做缓存,而是直接在 Web 服务器层上做,这样你就可以屏蔽 Java 语言层面的一些弱点;而相比起来,Web 服务器(如 Nginx、Apache、Varnish)也更擅长处理大并发的静态文件请求。
在哪些方面做动静分离
- URL 唯一化。商品详情系统天然地就可以做到 URL 唯一化,比如每个商品都由 ID 来标识,那么 http://item.xxx.com/item.htm?id=xxxx 就可以作为唯一的 URL 标识。为啥要 URL 唯一呢?前面说了我们是要缓存整个 HTTP 连接,那么以什么作为 Key 呢?就以 URL 作为缓存的 Key,例如以 id=xxx 这个格式进行区分。
- 分离浏览者相关的因素。浏览者相关的因素包括是否已登录,以及登录身份等,这些相关因素我们可以单独拆分出来,通过动态请求来获取。
- 分离时间因素。服务端输出的时间也通过动态请求获取。
- 异步化地域因素。详情页面上与地域相关的因素做成异步方式获取,当然你也可以通过动态请求方式获取,只是这里通过异步获取更合适。
= 去掉 Cookie。服务端输出的页面包含的 Cookie 可以通过代码软件来删除,如 Web 服务器 Varnish 可以通过 unset req.http.cookie 命令去掉Cookie。注意,这里说的去掉Cookie 并不是用户端收到的页面就不含 Cookie 了,而是说,在缓存的静态数据中不含有 Cookie。缓存架构
- 实体单机部署:部署服务器集群,单机上面存在Cache
- 统一Cache层:单独做Cache集群。
- CDN
二八原则:有针对性地处理好系统的“热点数据”
静态热点数据
- 动态热点数据:通过报名预约或者大数据分析就能将静态热点数据提前加载缓存中。
动态热点数据
- 构建一个异步的系统,它可以收集交易链路上各个环节中的中间件产品的热点 Key,如Nginx、缓存、RPC 服务框架等这些中间件(一些中间件产品本身已经有热点统计模块)。
- 建立一个热点上报和可以按照需求订阅的热点服务的下发规范,主要目的是通过交易链路上各个系统(包括详情、购物车、交易、优惠、库存、物流等)访问的时间差,把上游已经发现的热点透传给下游系统,提前做好保护。比如,对于大促高峰期,详情系统是最早知道的,在统一接入层上 Nginx 模块统计的热点 URL。
- 将上游系统收集的热点数据发送到热点服务台,然后下游系统(如交易系统)就会知道哪些商品会被频繁调用,然后做热点保护。这里我给出了一个图,其中用户访问商品时经过的路径有很多,我们主要是依赖前面的导购页面(包括首页、搜索页面、商品详情、购物车等)提前识别哪些商品的访问量高,通过这些系统中的中间件来收集热点数据,并记录到日志中。
流量削峰这事应该怎么做?
针对秒杀这一场景,
削峰从本质上来说就是更多地延缓用户请求的发出,以便减少和过滤掉一些无效请求,它遵从“请求数要尽量少”的原则。排队
- 1.消息队列,
- 2.利用线程池加锁等待也是一种常用的排队方式;
- 3.先进先出、先进后出等常用的内存排队算法的实现方式;
- 4.把请求序列化到文件中,然后再顺序地读文件(例如基于 MySQL binlog 的同步机制)来恢复请求等方式。
答题
- 第一个目的是防止部分买家使用秒杀器在参加秒杀时作弊。
- 第二个目的其实就是延缓请求,起到对请求流量进行削峰的作用,从而让系统能够更好地支持瞬时的流量高峰。
分层过滤
- 分层过滤的核心思想是:在不同的层次尽可能地过滤掉无效请求,让“漏斗”最末端的才是有效请求。
影响性能的因素有哪些?又该如何提高系统的性能?
影响系统的因素
- IO、网络、CPU、内存。
优化系统
- 减少编码,Java的编码运行是比较慢的,涉及到字符串和字节之间的转换,对网页的输出直接进行流输出。
- 减少序列化:可以采用合并部署,将应用放置在同一个tomcat容器中避免RPC。
- Java 极致优化
1.直接使用 Servlet 处理请求。避免使用传统的 MVC 框架,这样可以绕过一大堆复杂且用处不大的处理逻辑,节省 1ms 时间(具体取决于你对 MVC 框架的依赖程度)。
2.直接输出流数据。使用 resp.getOutputStream() 而不是 resp.getWriter() 函数,可以省掉一些不变字符数据的编码,从而提升性能;数据输出时推荐使用 JSON 而不是模板引擎(一般都是解释执行)来输出页面。- 并发读优化:用Cache进行冷热数据+动静数据。
减库存设计
常见的几种方式
- 下单减库存:比较安全
- 付款减库存:存在用户下单之后,无法付款
- 预扣库存:下单之后生成订单,订单会有时间保留,不付款则会失效。
如何限制超卖
- 事务、库存设置无符号整数、CASE WHEN
如何设计兜底方案?
- 降级,就是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。
- 限流,如果说降级是牺牲了一部分次要的功能和用户的体验效果,那么限流就是更极端的一种保护措施了。限流就是当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。
- 拒绝服务