秒杀系统剖析

一 概要

’秒杀主要解决两个问题,一个是并发读,一个是并发写。

并发读的核心优化理念是 尽量减少用户到服务端来‘读’数据,或者让他们读更少的数据;并发写的处理原则也一样,它要求我们在数据库层面独立出来一个库。另外我们还要针对秒杀系统做一些保护,针对意外的情况设计兜底方案,以防止最坏的情况发生。

  要想打造并维护一个超大流量并发读、高性能、高可用的系统,整个用户请求路径从浏览器到服务端我们要遵循几个原则,就是保证用户请求的数据尽量少、请求数尽量少、路径尽量短、依赖尽量少、不要单点(4要1不要)。

二 4要1不要说明

  • 数据尽量少:数据在网络上传需要时间,不管是请求数据还是返回数据都要服务器做处理,服务器数据传网络时需要做压缩和字符转码,这些都非常耗CPU。系统依赖的数据能少就少,调用其他服务会涉及数据的序列化和反序列化,这也是CPU的一大杀手,同样会增加延时。
  • 请求数尽量少: 浏览器每发出一个请求都会增加消耗,比如建立连接需要做三次握手,一些请求还需要串行加载(如JavaScript),不同的请求域名不一样的话,域名解析需要耗更多的时间。

解决方案:减少请求最常用的方案是合并CSS和JavaScript文件,把多个JavaScript文件合并成一个文件。

  • 路径尽量短:用户发出请求到发回数据这个过程中,需要经过的中间节点数尽量少。因为每增加一个节点会降低高可用性,节点之间数据通讯需要系列化和反系列化。

解决方案:把多个相互强依赖的应用合并部署在一起,把远程过程调用(RPC)变成JVM内部之间的方法调用。

  • 依赖尽量少:指完成一次用户请求必须依赖的系统或服务,这里的依赖是强依赖。比如展示秒杀页面必须强依赖商品信息、用户信息,弱依赖优惠券、成交列表,弱依赖不是非要不可的信息,在紧急情况下可以去掉

解决方案:要减少依赖可对系统进行分级,比如0级系统,1级系统,2级系统,0级系统如果是最重要的系统,0级系统强依赖的系统同样也是最重要的系统。0级系统尽量减少对其他系统的强依赖,防止重要的系统被不重要的系统拖垮。比如秒杀系统中支付系统是0级系统,优惠券是1级系统,在极端情况下可以把优惠券系统降级。

  • 不要有单点: 单点意味着没有备份,类似单机架构,风险不可控,分布式系统最重要的原则是‘消除单点’。

解决方案:关键点是避免将服务的状态和机器绑定,即把服务无状态化,使得服务可以在机器中随意移动。可以把和机器相关的配置动态化,这些参数配置可以通过配置中心动态推送,在服务启动时动态拉取下来。但存储服务本身很难无状态话,因为数据要存在磁盘上,本身就要和机器绑定,这种场景一般要通过冗余多个备份方式来解决单点问题。

三 稳准快说明

稳(高可用):整个系统架构要满足高可用,流量符合预期要稳,超出预期时同样也不能断链子,保证秒杀活动顺利完成。

解决方案:服务端极致的优化思路,设计planeB兜底方案

 

准(一致性):比如秒杀10台iPhone那就只能成交10台iPhone,多一台少一台都不行,准就是要在大并发更新的过程保证数据的一致性。

解决方案:一致性主要体现在减库存的方案,可分为‘拍下减库存’,‘付款减库存’,‘预扣减库存’等。

 

快(高性能):系统的性能要足够高,否则怎么支撑这么大的流量,不光是服务端要做极致的性能优化,而且在整个链路上都要做协同的优化,每个地方快一点,整个系统就完美了。

解决方案:①设计数据的动静分离;②热点的发现与隔离;③请求的削峰与分层过滤;④服务端的极致优化

四 秒杀系统架构改造性能提升方案

1 把秒杀系统(秒杀商品详情与秒杀交易系统等)独立出来单独打造一个系统,这样可以有针对性地做优化

2 在系统部署上也独立做一个机器群,这样秒杀的大流量就不会影响到正常的商品购买集群的机器负载

3 将热点数据单独放到一个缓存系统中,做独立部署,以提高‘读性能’

4 增加秒杀答题,防止有秒杀器抢单

5 对页面进行彻底的动静分离,使得用户秒杀时不需要刷新整个页面,而只需要向服务端请求很少的动态数据。

6 在服务端对秒杀商品进行本地缓存,不需要再调用依赖系统的后台服务获取数据,甚至不需要去公共的缓存集群中国查询数据,这样不仅可以减少系统调用,而且能够避免压垮公共缓存集群。

五 动静分离数据说明

动态数据和静态数据的主要区别是看页面中输出的数据是否和URL、浏览者、时间、地域相关,以及是否含有cookie等私密数据。所谓的动态还是静态并不是说本身是否动静,而是数据中是否含有和访问者相关的个性化参数。

1 怎么样对静态数据做缓存

  • 把静态数据存到离用户最近的地方,常见的如用户浏览器里、CDN上、服务端cache中;
  • 静态化改造,就是直接缓存HTTP连接而不是仅仅缓存数据,如web代理服务器根据请求的URL直接取出对应的HTTP响应头和响应体给请求方。要缓存URL连接就要确保URL的唯一性,以商品详情为例,以商品id拼在URL后缀(如http://www.jindong?id=xxx)就可以确保唯一,缓存时key用URL,value就是请求响应结果;
  • 谁来缓存静态数据也很重要。不同语言写的cache处理数据效率不一致,Java语言本身不擅长处理大量连接请求,每个连接消耗内存较多,servlet容器解析HTTP协议慢,所以可以不用Java层做缓存,而是直接在web服务器上做。常见的web服务器(Nginx,Apache,varnish)更擅长处理大并发的静态文件请求。

2 如何分离动态内容

  • URI唯一化,比如获取不同商品的详情,URL后缀加商品的id肯定会是动态化数据,这边也可以进行静态化改造,缓存整个URL响应内容;
  • 分离浏览者相关的因素,包括是否已登入,登入身份等,可以单独拆分开来通过动态请求获取
  • 获取时间因素,服务端的输出时间也通过动态请求获取
  • 异步地域因素,可以做成异步获取或动态获取
  • 去掉cookie,这里说的cookie并不是用户端收到的页面就不含cookie,而是在缓存的静态数据中不含有cookie

3 动态内容处理方案

静态内容一般用缓存处理就好,动态内容处理方案如下。

  • ESI(Edge Side Includes)方案或者SSI:即在web代理服务器上做动态内容请求,并将插入到静态页面中,当用户拿到时已经是一个完整的页面了。
  • CSI(client side include)方案:即单独发起一个异步JavaScript请求,以向服务端获取动态内容。

4 动静分类的几种架构方案

  • 实体机单机部署,Java应用程序服务和cache服务部署在同一台实体机上,优点是可以提高网路通讯没有网路瓶颈、可以提升缓存命中率、减少了cache失效压力;缺点是因为部署多种服务造成运维的难度;
  • 统一cache层,将cache和Java应用程序统一分离出来,优点是可以减少多个应用接入使用cache的成本,接入的应用维护自己的Java系统就好、统一cache方案更易于维护、可以共享内存;缺点主要是网路通讯问题,机器少风险大,挂掉一台就会影响很大一部分缓存数据;
  • 上CDN,使用CDN需要解决的问题

失效问题,就是让全国各地的缓存节点都是生效或失效,

命中率问题,需要确保全国各地节点命中同一个缓存结果,

发布更新问题,确保实时更新各地的cache。

因为网络通讯原因,节点放全国各地不太可能解决上述的问题,适合设置CDN节点的需要要满足以下条件:1 靠近访问量比较集中的地区;2 离主站相对较远;3 节点到主站间的网络比较好,而且稳定;

4 节点容量比较大,不会占用其他CDN太多的资源,5 节点不要太多。CDN化部署有以下几个特点:1 把整个页面缓存在用户浏览器中;2 如果强制刷新整个页面,也会请求CDN

 

六 热点数据说明

热点商品就是在很短时间内被大量用户执行访问、添加购物车、下单、支付等操作,称为热点操作。

1 为什么要关注热点

热点请求会占用大量的服务器处理资源,也可能出现这些热点请求是没有价值的无效请求,比如刷单,对系统资源完全是浪费,即便是有效的请求也要做正对性的优化。

2 如何发现热点数据

热点数据包括了静态热点数据和动态热点数据。静态热点数据可以通过商家报名的方式对热卖商品达标提前添加到缓存,还可以通过大数据分析每天top N访问量的商品。但这种统计方式实时性较差,能够动态的实时性发现热点最好。

发现动态热点数据的方案

①构建一个异步系统,它可以收集交易连路上各个环节中的中间件产品的热点key,如Nginx,缓存,RPC服务框架等中间件;

②建立一个热点上报和可以按照需求订阅的热点服务,主要目的是通过交易链路上的各个系统访问时间差,把上游已经发现的热点传透给下游系统,提前做好保护;

③将上游系统收集的热点数据发送到热点服务台,然后下游系统就会知道哪些商品会被频繁调用

3 处理热点数据

处理热点数据常用的思路:优化、限制、隔离

  • 优化,一般就是用缓存,缓存热点数据更多才有的是临时缓存,即不管静态数据还是动态数据,都用一个队列短暂地缓存数秒钟。
  • 限制,可对访问商品id做一致性hash,然后根据hash做分桶,每一个分桶设置一个处理队列,这样可以把热点商品限制在一个队列里,防止因某些热点商品占用太多的服务器资源,而使其他请求得不到服务器的处理资源。
  • 隔离,把热点商品单独进行隔离。隔离的方式分为 业务的隔离,然后让商家报名热点商品进行达标,提前预知热点商品;系统隔离,属于运行时的隔离,通过分组部署,秒杀可以申请单独的域名,让请求落到不同的集群中;数据隔离,对热点商品启用单独的cache集群或单独的数据库存放

七 秒杀系统如何削峰

服务器的处理资源是恒定的,用它或不用它处理能力都是一样,所以出现峰值的时候很容易导致处理不过来,闲的时候却又没有什么要处理。但是由于要保证服务质量,很多处理资源只能按照最忙的时候来预估,这会导致资源的一个浪费。削峰从本质上来说就是更多的地延缓用户请求的发出,以便减少和过滤掉一些无效请求,它遵从“请求数要尽量少”的原则。

流量削峰的方案:排队、答题、分层过滤,这些都是无损的方式,即不会损失用户的发出请求;还有一些强制措施,比如限流和机器负载。

1 排队

排队就是用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去。除了消息队列,类似的排队方式还有,①利用线程池加锁等待也是一种常用的排队方式;②先进先出、先进后出等常用的内存排队算法的实现方式;③把请求序列化到文件中,然后再顺序地读文件(例如基于maysql hinlog的同步机制)来恢复请求等方式

 

2 答题

采取答题的方式主要是为了增加购买的复杂度,从而达到防止部分买家使用秒杀器在参加秒杀时作弊,其次是延缓请求起到对请求流量削峰的作用。

秒杀答题的逻辑主要分为,① 题库生成模块,生成一个个不重复的题目和答案,②  题库的推送模块,用于在秒杀答题前把题目推送给详情系统和交易系统;3

③ 题目的图片生成模块,用户把图片生成为图片格式,并且在图片里面增加一些干扰因素,这里需要注意由于答题时网络比较拥挤,应该把题目的图片提前推送到CDN上并且进行预热(CDN可以把容器的静态资源拉取到本地存入缓存,用户可以从近距离的CDN上获取资源,减少网络时延),不然用户请求答题时图片可能加载比较慢。

 

 

3 分层过滤

分层过滤的核心思想是:在不同的层次尽可能地过滤掉无效请求,让“漏斗”最末端的才是有效请求。假如请求分别经过CDN、前台读系统(如商品详情系统)、后台系统(如交易系统)、数据库这几层,那么大部门数据和流量在用户浏览器或者CDN上获取这一层可以拦截大部分数据请求,经过前台系统时尽量走cache过滤掉一些无效请求,后台系统主要做数据的二次校验对系统做好保护和限流这样数据量的和请求可以进一步减少,最后在数据层完成数据的一致性校验。分层校验的基本原则:

① 将动态请求的读数据缓存在web端,过滤掉无效的数据请求;

② 对读数据不做强一致性校验,减少因为一致性校验产生的瓶颈问题;

③ 对写数据进行基于时间的合理分片,过滤掉过期的无效请求;

④对写请求做限流保护,将超出系统承载能力的请求过滤掉;

⑤ 对写数据进行强一致性校验,只保留最后有效的数据。

八 影响性能的因素有哪些?如何提高系统的性能

1 影响性能的因素

服务设备的不同对性能的定义也不一样,CPU主要看主频,磁盘主要看IOPS(input/output operations per second,即每秒进行读写操作的次数),系统的服务端性能一般用QPS(query per second,每秒请求数),和RT(response time即响应时间)来衡量。正常情况下响应时间越短,每秒种的请求数越多,但响应时间是有极限的,所以出现了另外一个维度多线程来处理请求。

   对于大部分web系统而言,响应时间等于CPU执行时间加等待时间(比如RPC,IO等待、sleep、wait等),根据实际测试发现,减少线程等待时间对性能的影响并不是线性关系,这一点在很多代理服务器上(proxy)可以做验证。我们每次给代理服务器的代理请求加个延时即响应时间,这对代理服务器本身的吞吐量并没有多大的影响,因为代理服务器本身的资源并没有被消耗,可以通过增加代理服务器的处理线程数,来弥补响应时间对代理服务器的QPS的影响。

   真正对性能有影响的是CPU的执行时间,因为CPU执行真正消耗了服务器的资源。实际中并不是线程数越多越好,因为线程本身也消耗资源,线程越多系统的线程切换成本就越高,每个线程都会消耗一定的内存。最佳的线程数等于=【(线程等待时间+线程CPU执行时间)/线程CPU执行时间】*CPU数量。

   就服务器而言,会出现瓶颈的地方很多,比如CPU、内存、磁盘以及网络等,一般缓存系统制约它的是内存,存储系统制约它的是I/O磁盘,秒杀系统制约它的就是CPU了。如何发现CPU的瓶颈呢?可以用CPU诊断工具,如jprofiler和yourkit等,它可以列出整个请求中每个函数的CPU执行时间,也可以采用jstack定时打印调用栈

2 如何优化系统

2.1 减少编码

编码运行比较慢是Java的一大硬伤,很多场景下只要涉及字符串的操作,比如io输入输出操作,都比较耗CPU资源,不管它是磁盘IO还是网络IO,都需要将字符转换成字节,转换必须编码,编码就需要查表,所以耗CPU资源。那如何减少编码呢,①页面输出可以直接进行流输出,比如response.getOutputStream()函数进行写数据;② 把一些静态的数据提前转换为字节,等到真正往外写的时候直接调用outputStream()函数写,就可以减少静态数据的编码转换

2. 减少系列化

系列化也是Java的一大性能硬伤,减少系列化也可以大大提升性能,因为系列化往往和编码同时发生,所以减少系列化就是减少了编码。

  系列化大部分是在RPC远程调用中发生,如何减少系列化,可以将关联性比较强的应用就行合并部署,就是减少不同应用直接的远程调用。

  所谓合并部署就是把原本在不同机器上不同应用合并部署在同一台机器上,更好的是部署在同一个Tomcat容器中,且不能走本机的socket,这样才能避免系列化的产生

2.3 Java极致优化

 Java和通用的web服务器(如Nginx或Apache)相比在处理大并发的HTTP请求时会弱一点,所以一般都会在处理大流量的web系统做静态化改造,让大部分请求和数据之间在Nginx服务器或者web代理服务器(如varnish,squid等)上直接返回,而Java层只需要处理少量的数据动态请求。这些动态请求可以做如下优化:①直接使用servlet处理请求,避免使用传统的MVC框架,这样可以绕过一大推复杂且用处不大的处理逻辑;②直接输出流数据,使用response.getOutputStream()而不是response.getWriter(),可以省掉一些不必要的字符数据编码,从而提升性能;③数据输出时推荐使用json而不是模板引擎来输出页面,模板引擎解释执行需要花很多时间。

2.4 并发读优化

  并发读可以将数据放在缓存中,集中式缓存为保证命中率(可以直接通过缓存获取到需要的数据)一般都会采用一致性hash,同一个key会落到同一台机器上。对于应对像“大秒”这种级别的热点商品可以采用应用层的localCache在秒杀系统的单机上缓存相关的商品信息彻底解决单机的瓶颈,那如何缓存数据呢?需要划分动态数据和静态数据进行处理,①静态数据像商品的标题和描述本身不变的数据,可以在秒杀前全量推送到秒杀系统上,并一直缓存到秒杀结束;②动态数据会采用’被动失效’的方式缓存一定的时间(一般毫秒),失效后再去缓存拉取最新的数据。频繁更新的动态数据,采用非实时的缓存可能会引起脏读,导致超卖(前端瞬时显示还有库存,表中其实没了),这种情况可以采用分层校验,允许读一定的脏数据,在后头入库的时候再保证最终的一致性。

 

九 秒杀系统“减库存”设计的核心逻辑

减库存的几种方式:下单减库存,付款减库存,预扣库存

1 下单减库存

即下单后马上减去库存,直接通过数据库的事务机制控制商品的库存,这样一定不会出现超卖,但是有些人下单后可能并不会付款,这容易导致竞争对手通过恶意下单的方式让这款商品的库存减为0。

2 付款减库存

即买家下单后不立即减库存,等待用户付款后才真正减库存,这会导致高并发的时候很多人下单但最后付款不了,因为商品被人抢走了,这容易导致下单成功数远远超过真正的库存数,存在了超卖的现象。

3 预扣库存

即买家下单后,库存为其保留一段时间(比如10分钟),超过这个时间不付款,库存将会自动释放,在买家付款前系统会校验该订单的库存是否还有保留,如果没有保留再次尝试预扣,如果库存不足则提示库存不足不允许付款,如果预扣成功完成付款后实际减去库存。但这样也不能彻底解决超卖的问题,因为恶意买家仍然可以在十分钟之后再次下单不付款,或者一次下单很多件的方式把库存减完。解决此类问题可以给经常下单不付款的买家进行识别打标(下单打标的买家下单不减库存)或者参加活动的商品限制购买数量和下单次数

 

4 秒杀减库存的极致优化

秒杀中并不需要对库存有精确的一致性(要确保最终的一致性),解决大并发问题可以把库存数据存入缓冲中,采用localCache(秒杀系统的单机上缓存商品相关的数据)和对数据进行分层过滤的方式,可以大大提高读的性能。

  如果秒杀系统减库存的逻辑比较单一,没有复杂的sku库存和总库存这种关联关系可以在缓存中减库存或者在一个带有持久化功能的缓存(如Redis)系统中减库存,否则需要使用事务的必须在数据库中完成减库存。

  根据MySQL的存储特点,因为同一数据在数据库里面是一行存储,对应热点同一商品在高并发的时候会有大量线程来竞争innoDB行锁,并发读越高等待的线程越多,TPS(transaction per second 每秒处理的消息数)会下降,RT(response time响应时间)会上升,吞吐量会受到严重的影响。会导致单一热点商品影响整个数据库的性能。解决并发锁的问题,可以采用如下的方案:

热点商品单独数据库隔离,但这会带来维护上的麻烦,比如热点数据的动态迁移

应用层做排队,按商品的维度设置队列顺序执行,主要能减少同一台机器对数据库同一行记录进行操作的并发度,同时也能控制某一商品占用数据库连接的数量,防止热点商品占用太多的数据库连接。但应用层只能做到单机的排队,秒杀系统机器本身很多,这种排队方式控制并发的能力仍然有限。

数据库层做排队,如果能在数据库层做全局的排队是最理想的,阿里的数据库团队开发了针对这种MySQL的innoDB层上的补丁程序(patch),可以在数据库层上对单元记录做并发的排队。

数据库层直接提交事务,InnoDB 内部的死锁检测,以及 MySQL Server 和 InnoDB 的切换会比较消耗性能,淘宝的 MySQL 核心团队还做了很多其他方面的优化,如 COMMIT_ON_SUCCESS 和 ROLLBACK_ON_FAIL 的补丁程序,配合在 SQL 里面加提示(hint),在事务里不需要等待应用层提交(COMMIT),而在数据执行完最后一条 SQL 后,直接根据 TARGET_AFFECT_ROW 的结果进行提交或回滚,可以减少网络等待时间(平均约 0.7ms)。目前阿里 MySQL 团队已经将包含这些补丁程序的 MySQL 开源。

合并脚本,还有些场景(如对商品的 lastmodifytime 字段的)更新会非常频繁,在某些场景下这些多条 SQL 是可以合并的,一定时间内只要执行最后一条 SQL 就行了,以便减少对数据库的更新操作。

 

 十 如何设计兜底方案

   对于秒杀系统虽然做了很多极致的优化思路以及架构的优化方案,但现实中难免发生一些这样或那样的意外,一个系统面临持续的大流量时,很难靠自身调整来恢复状态,必须等待流量的自然下降或者人为地把流量切走才行。

针对此问题我们可以在系统达到不可用状态之前就做好限流,防止最坏情况的发生。系统的高可用建设是一个系统工程,需要考虑到系统建设的各个阶段,贯穿整个生命周期。具体涉及到架构阶段、编码阶段、测试阶段、发布阶段、运行阶段、故障发生时。

    • 架构阶段:主要考虑系统的可扩展性和容错性,要避免系统出现单点问题。
    • 编码阶段:最重要的是保证代码的健壮性,例如远程调用问题时要设置合理的超时退出机制,也要对调用的返回结果集有预期,防止返回的结果超出程序处理范围。
    • 测试极端: 主要是保证测试用例的覆盖度,保证最坏情况发生时,我们也有相应的处理流程。
    • 发布阶段: 发布时也有一些地方需要注意,因为发布时最容易出现错误,要有紧急回滚的机制。
    • 运行阶段: 运行态最重要的是对系统的监控要准确及时,发现问题能够准确报警并且报警数据要准确详细,以便于排查问题。
    • 故障发生时: 故障发生时首先最重要的就是及时止损,例如由于程序问题导致商品价格错误,那就要及时下架商品或者关闭购买链接,防止造成重大资产损失,然后就要能够及时恢复服务,并定位原因解决问题。

针对秒杀这种大流量系统主要从运行阶段保障系统的稳定性。运行时保障系统稳定性的方案有:降级、限流、拒绝服务。

   ①降级:降级是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心的功能,从而把有限的资源留给更核心的业务。比如说当秒杀流量达到5w/s时把成交记录的获取从展示20条降级到只展示5条;又比如当11时如果优惠券系统扛不住,可以采取降级商品详情的优惠信息展示,把有限的系统资源用在保障交易系统正确展示优惠信息上,即保障用户真正下单时的价格是正确的。

   ②限流: 限流是当系统容量达到瓶颈时,通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。限流的实现方式既要支持URL一级方法的限流,也要支持基于QPS和线程的限流,总的来说分为客户端限流和服务端限流。QPS限流可以通过压测提前获取,比如系统最高支持1wQPS时,可以设置8000来进行限流保护;线程限流在客户端比较有限,例如在远程调用时设置连接池的线程数,超出这个并发数请求就讲线程进行排队或者直接超时丢弃。

   ③拒绝服务:当系统负载达到一定阈值时,例如CPU使用率达到90%或者系统load值达到2*CPU核数时,系统直接拒绝所有请求。在秒杀系统可以在最前端的Nginx上设置过载保护,当机器负载达到某个值时直接拒绝HTTP请求并返回503错误,在Java层同样也可以设置过载保护。要保证系统在过载时无法提供服务,但当负载下载是又很容易恢复服务。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值