专栏首页微观技术如何设计一个高性能的秒杀系统

如何设计一个高性能的秒杀系统

秒杀系统要如何架构,在做技术方案时要注意哪些问题,搞了个秒杀专辑,专门收集秒杀系列文章。

当你去一家公司面试时,很多面试官都会问你如何设计一个高性能秒杀系统。秒杀涉及的技术域从客户端、浏览器、网络、负载均衡、应用服务器、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、悲观锁

悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。虽然能解决安全问题,但是系统的吞吐量不高。

2、乐观锁

乐观锁采用“version”版本号控制更新,预设所有请求都有资格可以修改,并获得当前的版本号,如果版本号一致才能更新成功。

稳定性方面

重启与过载保护。如果系统发生“雪崩”,贸然重启服务,是无法解决问题的。最常见的现象是,启动起来后,立刻挂掉。这个时候,最好在入口层将流量拒绝,然后再将重启。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间。

秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的。这个时候,过载保护是必要的。如果检测到系统满负载状态,拒绝请求也是一种保护措施。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回。

风控方面

1、识别一些”秒杀器”、“刷票软件”的请求,并能有效拦截,杜绝资损,保证活动效果

2、一个账号,一次性发出多个请求,破坏了秒杀和抢购的公平性。后台服务有防刷机制,需要全局性的访问计数器控制,比如说一秒钟只允许一次请求。

3、多个账号,一次性发送多个请求。很多公司的账号注册功能,在发展早期几乎是没有限制的,很容易就可以注册很多个账号。因此,也导致了出现了一些特殊的工作室,通过编写自动注册脚本,积累了一大批“僵尸账号”,数量庞大,几万甚至几十万的账号不等,专门做各种刷的行为(这就是微博中的“僵尸粉“的来源)。解决方案:可以通过检测指定机器IP请求频率就可以解决,如果发现某个IP请求频率很高,可以给它弹出一个验证码或者直接禁止它的请求,弹出验证码,可以分辨出真实用户。当然市面也有专门的打码服务,通过机器学习自动识别验证码,所以验证码的防识别难度也在不断加大。比如:有些验证码采用随机答题库,防止秒杀器抢单。

4、多个账号,不同IP发送不同请求。目前市面上有专门卖代理服务的公司,提供代理IP,尽量模拟真实用户请求,这个时候,通常只能通过设置业务门槛高来限制这种请求了,或者通过账号行为的”数据挖掘“来提前清理掉它们。

僵尸账号也还是有一些共同特征的,例如账号很可能属于同一个号码段甚至是连号的,活跃度不高,等级低,资料不全等等。根据这些特点,适当设置参与门槛,例如限制参与秒杀的账号等级。通过这些业务手段,也是可以过滤掉一些僵尸号。

下面我们讲几个经典的设计案例

面对如此高的流量请求,如果是按常规模式,先让业务方评估秒杀规模,然后估算峰值流量,进而估算需要加多少台服务器,然后准备线上紧急扩容。活动结束后,还要将机器回收。劳民伤财,有没有更优的解决方案?

一、异步下单

异步化处理可以对流量进行削峰,满足高性能需求,同时不需要扩容太多的机器。

设计方案中,我们在前端和下单系统之间,增加一个排队系统。我们简单看下详细的处理过程:

1、用户访问商品详情页,然后提交订单请求,订单请求首先是进入请求队列,同时返回一个排队号码

2、前端会跳转到一个中间态的等待页,这个页面会根据排队号,定时地查询排队系统,排队系统会返回预订单在队列中的位置信息,包括它前面还有多少未处理的预订单,以及后台系统大概还要多久会处理这个预订单,这样用户就不会焦虑;

3、在排队系统的处理区,有很多消费者,它们依次从排队区的队列里获取预订单,然后调用后台下单系统生成实际的订单;

4、随着预订单变成正式的订单,队列里的预订单会逐渐变少,如果当前的预订单已经从队列里被移除了,用户的等待页就会检测到这个情况,页面自动跳转到订单完成页,这就和常规的购物流程一样了,用户进行最后的支付,最终完成整个前台下单过程。

上图是滴滴叫车的排队等待页,司机数代表系统的并发处理能力。如果,这片区域司机较少,叫车乘客较多的话,就会出现排队现象。

关于队列的技术选型,我们采用Redis 的List结构,更轻量级且性能更好。除了和 MQ 一样支持消息的先进先出以外,我们还可以获取队列的长度,以及通过排队号获取消息在队列中的位置,这样我们就可以给前端反馈预订单的处理进度。

另外,秒杀系统一般都是直接下单,不会采用购物车形式,一个订单往往只有一件商品。所以我们可以为每个商品提供一个单独的队列,将数据分片,多个队列可以提供更好的性能。

关于队列长度,为了保证用户能够买到商品,我们并不是把所有前台的下单请求都会放到队列里,而是根据参与活动的秒杀商品库存数量,按照 1:1 的比例,设置队列初始长度,这样就保证了进入队列的请求最终都能生成订单。这个可用队列长度会随着预订单进入队列,不断地减少,当数值变为 0 时,排队系统会拒绝接受新请求进入队列,直接反馈用户下单失败。当然,如果后台订单生成异常或用户取消订单后,可用队列长度会增加,前台会重新开放预订单进入队列。

任何事情都有两面性,有优势自然有不足。该方案适合瞬间有高并发流量,比如秒杀场景。如果该高峰期持续时间较长,同时注重用户体验,需要实时看到下单结果,则不适合采用异步下单方案。比如”外卖业务“,高峰期集中在中午11点到下午1点,从下单到送达一般在半个小时左右,对实时性要求很高。此时需要通过水平扩容,提升系统的处理能力。当然如果部署在阿里云,可以考虑弹性扩容、缩容,节省成本。

高可用建设

1、降级。所谓“降级”,就是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。它是一个有目的、有计划的执行过程,所以对降级我们一般需要有一套预案来配合执行。如果我们把它系统化,就可以通过预案系统和开关系统来实现降级。

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

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

本文分享自微信公众号 - 微观技术(weiguanjishu),作者:TomGE

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 如何通过Binlog来实现不同系统间数据同步

    互联网时代除了业务迭代速度快,还有就是数据增速也比较快。单应用、单实例、单数据库的时代早已不复返。现在,作为技术研发,如果参与的项目没有用到分库分表,都不好意说...

    用户7676729
  • APNS开源包的内存泄露问题

    APNS(全称:Apple Push Notification Service),主要是用于往苹果设备推送push消息通知!

    用户7676729
  • ReentrantLock知识点梳理

    接下来几篇文章会对JUC并发包里面的锁工具类做下梳理,如:ReentrantLock、

    用户7676729
  • 秒杀系统架构分析与实战,一文带你搞懂秒杀架构!

    (1)查询商品; (2)创建订单; (3)扣减库存; (4)更新订单; (5)付款; (6)卖家发货;

    Java技术栈
  • 秒杀系统架构分析与实战

    (1)查询商品; (2)创建订单; (3)扣减库存; (4)更新订单; (5)付款; (6)卖家发货;

    Java团长
  • 秒杀系统 架构分析 与 实战

    (1)查询商品; (2)创建订单; (3)扣减库存; (4)更新订单; (5)付款; (6)卖家发货;

    用户1516716
  • 这是我读过写得最好的【秒杀系统架构】分析与实战!

    假设某网站秒杀活动只推出一件商品,预计会吸引1万人参加活动,也就说最大并发请求数是10000,秒杀系统需要面对的技术挑战有:

    芋道源码
  • 纯干货--秒杀系统架构分析与实战

    (1)查询商品;(2)创建订单;(3)扣减库存;(4)更新订单;(5)付款;(6)卖家发货

    后端技术探索
  • 如何提升Web页面的性能,HTML和css代码优化!

    怎么进步Web页面的功用,许多开发人员从多个方面来下手如JavaScript、图画优化、服务器配置,文件压缩或是调整CSS。 很显然HTML 已经达到了一个瓶颈...

    企鹅号小编
  • python接口自动化(八)--发送post请求的接口(详解)

      上篇介绍完发送get请求的接口,大家必然联想到发送post请求的接口也不会太难,被聪明的你又猜到了。答案是对的,虽然发送post请求的参考例子很简单,但是实...

    北京-宏哥

扫码关注云+社区

领取腾讯云代金券