秒杀

秒杀系统面临的主要问题

总体上来说,秒杀发生的时候,因为同一时间大量用户交易的涌入系统,秒杀系统主要面临如下挑战:

1、高并发读

2、高并发写

流量的激增,很可能会导致如下的问题

1、网络带宽耗尽

2、服务器资源,包括cpu、内存等耗尽

3、数据库瘫痪

4、高并发写下的数据一致性

架构方案和思路

高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。需要重点设计数据的动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化这 4 个方面。

一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性。

高可用。 现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,我们还要设计一个 PlanB 来兜底,以便在最坏情况发生时仍然能够从容应对。

从架构设计原则上说,需要做到

1、数据要尽量少

所谓“数据要尽量少”,首先是指用户请求的数据能少就少。请求的数据包括上传给系统的数据和系统返回给用户的数据(通常就是网页)。

2、请求数要尽量少

用户请求的页面返回后,浏览器渲染这个页面还要包含其他的额外请求,比如说,这个页面依赖的 CSS/JavaScript、图片,以及 Ajax 请求等等都定义为“额外请求”,这些额外请求应该尽量少。因为浏览器每发出一个请求都多少会有一些消耗,例如建立连接要做三次握手,有的时候有页面依赖或者连接数限制,一些请求(例如 JavaScript)还需要串行加载等。另外,如果不同请求的域名不一样的话,还涉及这些域名的 DNS 解析,可能会耗时更久。所以你要记住的是,减少请求数可以显著减少以上这些因素导致的资源消耗。

3、路径要尽量

短所谓“路径”,就是用户发出请求到返回数据这个过程中,需求经过的中间的节点数。

4、依赖要尽量少

所谓依赖,指的是要完成一次用户请求必须依赖的系统或者服务,这里的依赖指的是强依赖。

静态数据的处理:

1、URL 唯一化。商品详情系统天然地就可以做到 URL 唯一化,比如每个商品都由 ID 来标识,那么 http://item.xxx.com/item.htm?id=xxxx 就可以作为唯一的 URL 标识。为啥要 URL 唯一呢?前面说了我们是要缓存整个 HTTP 连接,那么以什么作为 Key 呢?就以 URL 作为缓存的 Key,例如以 id=xxx 这个格式进行区分。

2、分离浏览者相关的因素。浏览者相关的因素包括是否已登录,以及登录身份等,这些相关因素我们可以单独拆分出来,通过动态请求来获取。

3、分离时间因素。服务端输出的时间也通过动态请求获取。

4、异步化地域因素。详情页面上与地域相关的因素做成异步方式获取,当然你也可以通过动态请求方式获取,只是这里通过异步获取更合适。

5、去掉 Cookie。服务端输出的页面包含的 Cookie 可以通过代码软件来删除,如 Web 服务器 Varnish 可以通过 unset req.http.cookie 命令去掉 Cookie。注意,这里说的去掉 Cookie 并不是用户端收到的页面就不含 Cookie 了,而是说,在缓存的静态数据中不含有 Cookie。

动态内容的处理通常有两种方案:ESI(Edge Side Includes)方案和 CSI(Client Side Include)方案。

ESI 方案(或者 SSI):即在 Web 代理服务器上做动态内容请求,并将请求插入到静态页面中,当用户拿到页面时已经是一个完整的页面了。这种方式对服务端性能有些影响,但是用户体验较好。

CSI 方案。即单独发起一个异步 JavaScript 请求,以向服务端获取动态内容。这种方式服务端性能更佳,但是用户端页面可能会延时,体验稍差。

对于热点数据,需要发现热点数据,处理热点数据

流量削峰

排队

对发出来的请求进行缓冲,而针对秒杀场景还有一种方法,就是对请求进行分层过滤,从而过滤掉一些无效的请求。分层过滤其实就是采用“漏斗”式设计来处理请求的,分层过滤假如请求分别经过 CDN、前台读系统(如商品详情系统)、后台系统(如交易系统)和数据库这几层,那么:大部分数据和流量在用户浏览器或者 CDN 上获取,这一层可以拦截大部分数据的读取;经过第二层(即前台系统)时数据(包括强一致性的数据)尽量得走 Cache,过滤一些无效的请求;再到第三层后台系统,主要做数据的二次检验,对系统做好保护和限流,这样数据量和请求就进一步减少;最后在数据层完成数据的强一致性校验。

秒杀注意点:

新建系统承接秒杀压力,而不是在 旧系统上改造(或者单独部署秒杀系统,不对正常系统产生影响)

租借秒杀活动网络带宽

动态生成随机下单页面 URL

秒杀系统设计原则:

1.静态化

2.并发控制,防秒杀器

3.简化流程

4.前端优化

架构设计原则

  • 数据要尽量少
  • 请求要尽量少
  • 路径要尽量短
  • 依赖要尽量少
  • 不要有单点

细节注意点:

1.如何控制秒杀购买按钮点亮

开始前,静态页面,缓存在cdn和反向代理服务器上,刷新请求到不了后面的服务器。

使用 JavaScript 脚本控制,在秒杀商品静态页面中加入一个 JavaScript 文 件引用,该 JavaScript 文件中加入秒杀是否开始的标志和下单页面 URL 的随机数参数, 当秒杀开始的时候生成一个新的 JavaScript 文件并被用户浏览器加载,控制秒杀商品页面 的展示。这个 JavaScript 文件使用随机版本号,并且不被浏览器、CDN 和反向代理服务 器缓存。

2. 如何只允许第一个提交的订单被发送到订单子系统

由于最终能够成功秒杀到商品的用户只有一个,因此需要在用户提交订单时,检查 是否已经有订单提交。事实上,由于最终能够成功提交订单的用户只有一个,为了减轻 下单页面服务器的负载压力,可以控制进入下单页面的入口,只有少数用户能进入下单 页面,其他用户直接进入秒杀结束页面。假设下单服务器集群有 10 台服务器,每台服务 器只接受最多 10 个下单请求

针对秒杀,各类服务器要求参数调优

页面优化,交易性能优化

PlanB,兜底方案

高可用

降级

限流

拒绝服务

当系统负载达到一定阈值时,例如 CPU 使用率达到 90% 或者系统 load 值达到 2*CPU 核数时,系统直接拒绝所有请求,这种方式是最暴力但也最有效的系统保护方式。例如秒杀系统,我们在如下几个环节设计过载保护:在最前端的 Nginx 上设置过载保护,当机器负载达到某个值时直接拒绝 HTTP 请求并返回 503 错误码,在 Java 层同样也可以设计过载保护。拒绝服务可以说是一种不得已的兜底方案,用以防止最坏情况发生,防止因把服务器压跨而长时间彻底无法提供服务。像这种系统过载保护虽然在过载时无法提供服务,但是系统仍然可以运作,当负载下降时又很容易恢复,所以每个系统和每个环节都应该设置这个兜底方案,对系统做最坏情况下的保护。

搜索引擎