---
title: 如何设计一个高性能的秒杀系统
---
# 如何设计一个高性能的秒杀系统
> 作者:Tom哥
>
公众号:微观技术
>
博客:[https://offercome.cn](https://offercome.cn)
>
人生理念:知道的越多,不知道的越多,努力去学
秒杀系统要如何架构,在做技术方案时要注意哪些问题,搞了个秒杀专辑,专门收集秒杀系列文章。
当你去一家公司面试时,很多面试官都会问你如何设计一个高性能秒杀系统。秒杀涉及的技术域从客户端、浏览器、网络、负载均衡、应用服务器、CDN、静态化、库存超卖、流量排队、流控、各种缓存组合、数据库存储等,非常之多,整个后端领域知识基本都会用到。比起普通的业务系统 ,秒杀对技术要求无论在深度还是广度都非常之高,很容易全面考察候选人的技术水平。
当然不同公司、不同业务场景,在系统设计灵活性、技术框架选型可能也会有不同,如何用最少的成本满足业务需求,才是最靓的技术方案,所以也能考察候选人的思维应变能力。
## 秒杀特征
1、活动一般都是整点开始,一瞬间会有大量的用户流量涌入,流量可能是平时的几十倍,系统QPS非常高,因此对系统的性能要求非常高
2、虽然流量非常高,但是与常规业务不同,不是每个用户请求都是要对其负责并处理。简单来说,可以响应标准化错误文案。
3、持续时间非常短,往往只有几秒钟,长的话也可能只有几分钟
4、活动商品一般都会有库存限制,一定要控制好并发,不能因为高并发引发了库存超卖
## 面临的挑战
### 1、现有业务的风险隔离
秒杀活动属于营销玩法,带动网站氛围。具有时间短、并发高的特点。对网站正常业务可能会有影响。我们一般会将秒杀系统单独部署,采用独立域名,从物理资源层面做到风险隔离。
### 2、前台用户频繁刷新,数据库的负载较高
一般会引入缓存机制,商品详情页面静态化处理,放入CDN,用户可以从最近的CDN节点拉取内容。动态的内容,比如库存,采用ajax异步化形式从中心服务器获取,当然后面应用服务器也会有缓存机制。
### 3、秒杀器限制
为了避免用户直接访问下单页面URL,需要将URL动态化,每次打开详情页时动态生成一个随机数,用于后端校验请求的合法性。
### 4、评估网络带宽
计算商品页面的大小,然后可以算出需要的网络带宽,(网络带宽= 单个页面大小*QPS),如果带宽不足需要及时购买。同时大部分的带宽都是图片资源,我们可以考虑将图片、js、css等信息缓存到CDN。
## 架构原则
1、数据要尽量少。请求的数据包括上传给系统的数据和系统返回给用户的数据(通常就是网页)。因为首先这些数据在网络上传输需要时间,其次不管是请求数据还是返回数据都需要服务器做处理,而服务器在写网络时通常都要做压缩和字符编码,这些都非常消耗 CPU,所以减少传输的数据量可以显著减少 CPU 的使用。例如,我们可以简化秒杀页面的大小,去掉不必要的页面装修效果,等等。
2、请求数要尽量少。用户请求的页面返回后,浏览器渲染这个页面还要包含其他的额外请求,比如说,这个页面依赖的 CSS/JavaScript、图片,以及 Ajax 请求等等都定义为“额外请求”,这些额外请求应该尽量少。因为浏览器每发出一个请求都多少会有一些消耗,例如建立连接要做三次握手,有的时候有页面依赖或者连接数限制,一些请求(例如 JavaScript)还需要串行加载等。另外,如果不同请求的域名不一样的话,还涉及这些域名的 DNS 解析,可能会耗时更久。所以你要记住的是,减少请求数可以显著减少以上这些因素导致的资源消耗。
> 例如,减少请求数最常用的一个实践就是合并 CSS 和 JavaScript 文件,把多个 JavaScript 文件合并成一个文件,在 URL 中用逗号隔开(https://g.xxx.com/tm/xx-b/4.0.94/mods/??module-preview/index.xtpl.js,module-jhs/index.xtpl.js,module-focus/index.xtpl.js)。这种方式在服务端仍然是单个文件各自存放,只是服务端会有一个组件解析这个 URL,然后动态把这些文件合并起来一起返回。
3、路径要尽量短。所谓“路径”,就是用户发出请求到返回数据这个过程中,需要经过的中间的节点数。
所以缩短请求路径不仅可以增加可用性,同样可以有效提升性能(减少中间节点可以减少数据的序列化与反序列化),并减少延时(可以减少网络传输耗时)。
4、依赖要尽量少。举个例子,比如说你要展示秒杀页面,而这个页面必须强依赖商品信息、用户信息,还有其他如优惠券、成交列表等这些对秒杀不是非要不可的信息(弱依赖),这些弱依赖在紧急情况下就可以去掉。
5、系统中的单点可以说是系统架构上的一个大忌,因为单点意味着没有备份,风险不可控,我们设计分布式系统最重要的原则就是“消除单点”。避免将服务的状态和机器绑定,即把服务无状态化,这样服务就可以在机器中随意移动。
## 秒杀架构设计
接下来,从产品-->前端-->后端--->数据存储,庖丁解牛讲解各个模块的设计思路,以及要注意的问题。
### 产品层
秒杀系统为秒杀而设计,不同于一般的网购行为,参与秒杀活动的用户更关心的是如何能快速刷新商品页面,在秒杀开始的时候抢先进入下单页面,而不是商品详情等用户体验细节,因此秒杀系统的页面设计应尽可能简单。
商品页面中的购买按钮只有在秒杀活动开始的时候才变亮,在此之前及秒杀商品卖出后,该按钮都是灰色的,不可以点击。
下单表单也尽可能简单,购买数量只能是一个且不可以修改,送货地址和付款方式都使用用户默认设置,没有默认也可以不填,允许在订单提交后再修改;只有第一个提交的订单发送给网站的订单子系统,其余用户提交订单后只能看到秒杀结束页面。
### 前端层
1、html页面一般比较大,即使做了压缩,http头和内容的大小也可能高达数十K,加上其他的css, js,图片等资源,如果同时有几千万人参与一个商品的抢购,一般机房带宽也就只有1G10G,网络带宽就极有可能成为瓶颈,所以这个页面上各类静态资源首先应分开存放,然后放到cdn节点上分散压力,由于CDN节点遍布全国各地,能缓冲掉绝大部分的压力,而且还比机房带宽便宜
2、秒杀倒计时。这个时间可以从后端实时获取,避免本地时间不准(另外本地的系统时间可以随意更改)。如果活动时间未到,下单按钮置灰,不允许下单购买。
3、用户点击“查询”或者“购票”后,按钮置灰,等待结果返回,禁止用户重复提交请求。JS层面,限制用户在x秒之内只能提交一次请求;
### 后端层
前端层的请求拦截,只能拦住小白用户(不过这是99%的用户),高端的程序员根本不吃这一套,写个for循环,直接调用你后端的http请求,怎么整?
同一个uid,限制访问频度,做页面缓存,x秒内到达站点层的请求,均返回同一页面
同一个item的查询,例如手机车次,做页面缓存,x秒内到达站点层的请求,均返回同一页面
如此限流,又有99%的流量会被拦截在站点层。
前两层只能拦住普通程序员,高级黑客,假设他控制了10w台肉鸡(并且假设买票不需要实名认证),这下uid的限制不行了吧?怎么整?
用户请求分发模块:使用Nginx或Apache将用户的请求分发到不同的机器上。
用户请求预处理模块:判断商品是不是还有剩余来决定是不是要处理该请求。
用户请求处理模块:把通过预处理的请求封装成事务提交给数据库,并返回是否成功。
数据库接口模块:该模块是数据库的唯一接口,负责与数据库交互,提供RPC接口供查询是否秒杀结束、剩余数量等信息。
### 数据库设计
分片解决的是“数据量太大”的问题,也就是通常说的“水平切分”。一旦引入分片,势必有“数据路由”的概念,哪个数据访问哪个库。路由规则通常有3种方法:
1、范围:range
优点:简单,容易扩展
缺点:各库压力不均(新号段更活跃)
2、哈希:hash 【大部分互联网公司采用的方案二:哈希分库,哈希路由】
优点:简单,数据均衡,负载均匀
缺点:迁移麻烦(2库扩3库数据要迁移)
3、路由服务:router-config-server
优点:灵活性强,业务与路由算法解耦
缺点:每次访问数据库前多一次查询
### 库存超卖
秒杀的并发非常高,库存只有一条记录,如果控制不当,很容易产生超卖现象,解决思路无非就是加锁,分为两种:
**1、悲观锁**