高并发架构设计都有哪些关键点

高并发架构设计都有哪些关键点?

秒杀其实主要解决两个问题,一个是并发读,一个是并发写前端

其实,秒杀的总体架构能够归纳为“稳、准、快”几个关键字java

而后就是“准”,就是秒杀 10 台 iPhone,那就只能成交 10 台,多一台少一台都不行。一旦库存不对,那平台就要承担损失,因此“准”就是要求保证数据的一致性。redis

最后再看“快”,“快”其实很好理解,它就是说系统的性能要足够高,不然你怎么支撑这么大的流量呢?不光是服务端要作极致的性能优化,并且在整个请求链路上都要作协同的优化,每一个地方快一点,整个系统就完美了。算法

因此从技术角度上看“稳、准、快”,就对应了咱们架构上的高可用、一致性和高性能的要求,咱们的专栏也将主要围绕这几个方面来展开,具体以下。数据库

  • 高性能。 秒杀涉及大量的并发读和并发写,所以支持高并发访问这点很是关键。本专栏将从设计数据的动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化这 4 个方面重点介绍。
  • 一致性。 秒杀中商品减库存的实现方式一样关键。可想而知,有限数量的商品在同一时刻被不少倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程当中都要保证数据的准确性,其难度可想而知。所以,我将用一篇文章来专门讲解如何设计秒杀减库存方案。
  • 高可用。 虽然我介绍了不少极致的优化思路,但现实中总不免出现一些咱们考虑不到的状况,因此要保证系统的高可用和正确性,咱们还要设计一个 PlanB 来兜底,以便在最坏状况发生时仍然可以从容应对。专栏的最后,我将带你思考能够从哪些环节来设计兜底方案。

设计高并发系统时应该注意的5个架构原则

高并发系统本质上就是一个知足大并发、高性能和高可用的分布式系统

设计原则

数据操做要尽可能少

  1. 请求的数据能少就少。请求的数据包括上传给系统的数据和系统返回给用户的数据
  2. 返回的数据能少就少, 减小后端序列化的时间
  3. 数据库操做能少就少 , 这个就不说了

请求数要尽可能少

这个就很直观了 , 请求数少并发量就少后端

调用链路要尽可能短

高并发系统 1. 要下降系统依赖 , 防止由于依赖形成的各类问题 , 提升可用性 2. 下降流量入侵 , 大流量尽可能隔绝在外面浏览器

不要有单点

系统中的单点能够说是系统架构上的一个大忌,由于单点意味着没有备份,风险不可控 , 其次流量不能分发像redis这种会有热点数据问题缓存

一些基本案例

其实构建一个高并发系统并无那么复杂 , 有一下的几个方法能够扛住比较高的并发性能优化

  1. 把高并发系统独立出来单独打造一个系统,这样能够有针对性地作优化,例如这个独立出来的系统就减小了店铺装修的功能,减小了页面的复杂度
  2. 在系统部署上也独立作一个机器集群,这样秒杀的大流量就不会影响到正常的商品购买集群的机器负载
  3. 将热点数据(如库存数据)单独放到一个缓存系统中,以提升“读性能”
  4. 增长秒杀答题,防止有秒杀器抢单
  5. 一些数据放入cdn中进行缓存
  6. 使用本地缓存部分数据

对电商来讲系统差很少是这种样子 :服务器

高并发系统场景优化1 - 动静分离

所谓动静分离 , 就是将一些不常变化 , 能够静态化 , 无状态 , 不须要逻辑处理的一些字段放在一个专门的系统或者地方 , 获取的时候不须要走后端系统的方法

动静分离原则

1. 把静态数据缓存到离用户最近的地方

常见的就三种 , 用户浏览器里、CDN 上 或者 在服务端的 Cache 中

2. 缓存http连接信息减小解析过程

Web 代理服务器根据请求URL查找缓存,直接取出对应的 HTTP 响应头和响应体而后直接返回,这个响应过程简单得连 HTTP 协议都不用从新组装,甚至连 HTTP 请求头也不须要解析

如何动静分离改造

静态数据处理方法

  1. URL 惟一化 : 为啥要 URL 惟一呢?前面说了咱们是要缓存整个 HTTP 链接,那么以什么做为 Key 呢?就以 URL 做为缓存的 Key,例如以 id=xxx 这个格式进行区分
  2. 分离浏览者相关的因素。浏览者相关的因素包括是否已登陆,以及登陆身份等,这些相关因素咱们能够单独拆分出来,经过动态请求来获取
  3. 分离时间因素。服务端输出的时间也经过动态请求获取。
  4. 异步化地域因素。详情页面上与地域相关的因素作成异步方式获取,固然你也能够经过动态请求方式获取,只是这里经过异步获取更合适。
  5. 去掉 Cookie。服务端输出的页面包含的 Cookie 能够经过代码软件来删除,如 Web 服务器 Varnish 能够经过 unset req.http.cookie 命令去掉 Cookie。注意,这里说的去掉 Cookie 并非用户端收到的页面就不含 Cookie 了,而是说,在缓存的静态数据中不含有 Cookie

动态数据处理方法

这个没有什么好办法 , 动态数据必定会将流量打到后端 , 因此尽量的减小这部分 , 若是不行就加机器

动静分离的几种架构方案

有 3 种方案可选:

  1. 单机本地cache层
  2. 统一 Cache 层
  3. 上 CDN。

1. 单机本地cache层

就是使用内存缓存好比java的ehcache等

优势 缺点
无网络开销 占用内存大
使用简单 同步机制须要使用其余方法保证

统一 Cache 层

典型的就是redis集群

优势 缺点
StartFragment单独一个 Cache 层,能够减小多个应用接入时使用 Cache 的成本。这样接入的应用只要维护本身的 Java 系统就好,不须要单独维护 Cache,而只关心如何使用便可 EndFragment StartFragmentCache 层内部交换网络成为瓶颈 EndFragment
StartFragment统一 Cache 的方案更易于维护,如后面增强监控、配置的自动化,只须要一套解决方案就行,统一块儿来维护升级也比较方便。 EndFragment StartFragment缓存服务器的网卡也会是瓶颈; EndFragment
StartFragment能够共享内存,最大化利用内存,不一样系统之间的内存能够动态切换,从而可以有效应对各类攻击。 EndFragment StartFragment机器少风险较大,挂掉一台就会影响很大一部分缓存数据。 EndFragment
要解决上面这些问题,能够再对 Cache 作 Hash 分组,即一组 Cache 缓存的内容相同,这样可以避免热点数据过分集中致使新的瓶颈产生。 好比redis 热点数据分组

上 CDN

在将整个系统作动静分离后,咱们天然会想到更进一步的方案,就是将 Cache 进一步前移到 CDN 上,由于 CDN 离用户最近,效果会更好

有如下几个问题须要解决

  1. 失效问题。前面咱们也有提到过缓存时效的问题,不知道你有没有理解,我再来解释一下。谈到静态数据时,我说过一个关键词叫“相对不变”,它的言外之意是“可能会变化”。好比一篇文章,如今不变,但若是你发现个错别字,是否是就会变化了?若是你的缓存时效很长,那用户端在很长一段时间内看到的都是错的。因此,这个方案中也是,咱们须要保证 CDN 能够在秒级时间内,让分布在全国各地的 Cache 同时失效,这对 CDN 的失效系统要求很高
  2. 命中率问题。Cache 最重要的一个衡量指标就是“高命中率”,否则 Cache 的存在就失去了意义。一样,若是将数据所有放到全国的 CDN 上,必然致使 Cache 分散,而 Cache 分散又会致使访问请求命中同一个 Cache 的可能性下降,那么命中率就成为一个问题。
  3. 发布更新问题。若是一个业务系统每周都有平常业务须要发布,那么发布系统必须足够简洁高效,并且你还要考虑有问题时快速回滚和排查问题的简便性。

由于上面的这些问题 , 因此cdn的部署方法通常都是分网络分区域的中心化部署

高并发系统场景优化2 - 处理热点

要关注热点

首先,热点请求会大量占用服务器处理资源,虽然这个热点可能只占请求总量的亿分之一,然而却可能抢占 90% 的服务器资源,若是这个热点请求仍是没有价值的无效请求,那么对系统资源来讲彻底是浪费。

其次,即便这些热点是有效的请求,咱们也要识别出来作针对性的优化,从而用更低的代价来支撑这些热点请求

热点操做和热点数据

所谓“热点操做”,例如大量的刷新页面、大量的添加购物车、双十一零点大量的下单等都属于此类操做。对系统来讲,这些操做能够抽象为“读请求”和“写请求”,这两种热点请求的处理方式截然不同,读请求的优化空间要大一些,而写请求的瓶颈通常都在存储层,优化的思路就是根据 CAP 理论作平衡

热点数据”比较好理解,那就是用户的热点请求对应的数据。而热点数据又分为“静态热点数据”和“动态热点数据”

静态热点数据和动态热点数据

所谓“静态热点数据”,就是可以提早预测的热点数据。例如,咱们能够经过卖家报名的方式提早筛选出来,经过报名系统对这些热点商品进行打标。另外,咱们还能够经过大数据分析来提早发现热点商品,好比咱们分析历史成交记录、用户的购物车记录,来发现哪些商品可能更热门、更好卖,这些都是能够提早分析出来的热点

所谓“动态热点数据”,就是不能被提早预测到的,系统在运行过程当中临时产生的热点。例如,卖家在抖音上作了广告,而后商品一下就火了,致使它在短期内被大量购买

咱们如何处理热点数据

发现热点数据

热点数据静态发现

静态热点数据能够经过商业手段,例如强制让卖家经过报名参加的方式提早把热点商品筛选出来,实现方式是经过一个运营系统,把参加活动的商品数据进行打标,而后经过一个后台系统对这些热点商品进行预处理,如提早进行缓存 . 或者使用技术手段提早预测,例如对买家天天访问的商品进行大数据计算,而后统计出 TOP N 的商品,咱们能够认为这些 TOP N 的商品就是热点商品。

热点数据动态发现

主要处理动态热点数据的 , 都是使用技术手段实现的

  1. 构建一个异步的系统,它能够收集交易链路上各个环节中的中间件产品的热点 Key,如 Nginx、缓存、RPC 服务框架等这些中间件(一些中间件产品自己已经有热点统计模块)。
  2. 创建一个热点上报和能够按照需求订阅的热点服务的下发规范,主要目的是经过交易链路上各个系统(包括详情、购物车、交易、优惠、库存、物流等)访问的时间差,把上游已经发现的热点透传给下游系统,提早作好保护。好比,对于大促高峰期,详情系统是最先知道的,在统一接入层上 Nginx 模块统计的热点 URL。
  3. 将上游系统收集的热点数据发送到热点服务台,而后下游系统(如交易系统)就会知道哪些商品会被频繁调用,而后作热点保护。

这里我给出了一个图,其中用户访问商品时通过的路径有不少,咱们主要是依赖前面的导购页面(包括首页、搜索页面、商品详情、购物车等)提早识别哪些商品的访问量高,经过这些系统中的中间件来收集热点数据,并记录到日志中。

咱们经过部署在每台机器上的 Agent 把日志汇总到聚合和分析集群中,而后把符合必定规则的热点数据,经过订阅分发系统再推送到相应的系统中。你能够是把热点数据填充到 Cache 中,或者直接推送到应用服务器的内存中,还能够对这些数据进行拦截,总之下游系统能够订阅这些数据,而后根据本身的需求决定如何处理这些数据。

打造热点发现系统时,我根据以往经验总结了几点注意事项。

  1. 这个热点服务后台抓取热点数据日志最好采用异步方式,由于“异步”一方面便于保证通用性,另外一方面又不影响业务系统和中间件产品的主流程。
  2. 热点服务发现和中间件自身的热点保护模块并存,每一个中间件和应用还须要保护本身。热点服务台提供热点数据的收集和订阅服务,便于把各个系统的热点数据透明出来。
  3. 热点发现要作到接近实时(3s 内完成热点数据的发现),由于只有作到接近实时,动态发现才有意义,才能实时地对下游系统提供保护。

处理热点数据

处理热点数据一般有几种思路:一是优化,二是限制,三是隔离。

优化

优化热点数据最有效的办法就是缓存热点数据,若是热点数据作了动静分离,那么能够长期缓存静态数据。可是,缓存热点数据更多的是“临时”缓存,即无论是静态数据仍是动态数据,都用一个队列短暂地缓存数秒钟,因为队列长度有限,能够采用 LRU 淘汰算法替换。

限制

限制更多的是一种保护机制,限制的办法也有不少,例如对被访问商品的 ID 作一致性 Hash,而后根据 Hash 作分桶,每一个分桶设置一个处理队列,这样能够把热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源,而使其余请求始终得不到服务器的处理资源。

隔离

高并发系统设计的第一个原则就是将这种热点数据隔离出来,不要让 1% 的请求影响到另外的 99%,隔离出来后也更方便对这 1% 的请求作针对性的优化。

具体到“秒杀”业务,咱们能够在如下几个层次实现隔离。
  1. 业务隔离。把秒杀作成一种营销活动,卖家要参加秒杀这种营销活动须要单独报名,从技术上来讲,卖家报名后对咱们来讲就有了已知热点,所以能够提早作好预热。
  2. 系统隔离。系统隔离更多的是运行时的隔离,能够经过分组部署的方式和另外 99% 分开。秒杀能够申请单独的域名,目的也是让请求落到不一样的集群中。
  3. 数据隔离。秒杀所调用的数据大部分都是热点数据,好比会启用单独的 Cache 集群或者 MySQL 数据库来放热点数据,目的也是不想 0.01% 的数据有机会影响 99.99% 数据。

固然了,实现隔离有不少种办法。好比,你能够按照用户来区分,给不一样的用户分配不一样的 Cookie,在接入层,路由到不一样的服务接口中;再好比,你还能够在接入层针对 URL 中的不一样 Path 来设置限流策略。服务层调用不一样的服务接口,以及数据层经过给数据打标来区分等等这些措施,其目的都是把已经识别出来的热点请求和普通的请求区分开

高并发系统场景优化3 - 流量削峰

为何要削峰

咱们知道服务器的处理资源是恒定的,你用或者不用它的处理能力都是同样的,因此出现峰值的话,很容易致使忙处处理不过来,闲的时候却又没有什么要处理。可是因为要保证服务质量,咱们的不少处理资源只能按照忙的时候来预估,而这会致使资源的一个浪费

流量削峰的一些操做思路:排队、答题、分层过滤

排队

要对流量进行削峰,最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送,中间经过一个队列在一端承接瞬时的流量洪峰,在另外一端平滑地将消息推送出去

若是流量峰值持续一段时间达到了消息队列的处理上限,例如本机的消息积压达到了存储空间的上限,消息队列一样也会被压垮,这样虽然保护了下游的系统,可是和直接把请求丢弃也没多大的区别

消息队列,相似的排队方式还有不少,例如:

  1. 利用线程池加锁等待也是一种经常使用的排队方式;
  2. 先进先出、先进后出等经常使用的内存排队算法的实现方式;
  3. 把请求序列化到文件中,而后再顺序地读文件(例如基于 MySQL binlog 的同步机制)来恢复请求等方式。

能够看到,这些方式都有一个共同特征,就是把“一步的操做”变成“两步的操做”,其中增长的一步操做用来起到缓冲的做用。

答题

增长答题其实有不少目的

  1. 第一个目的是防止部分买家使用秒杀器在参加秒杀时做弊 , 过滤无用请求
  2. 延缓请求,起到对请求流量进行削峰的做用,从而让系统可以更好地支持瞬时的流量高峰

分层过滤

前面介绍的排队和答题要么是少发请求,要么对发出来的请求进行缓冲,而针对秒杀场景还有一种方法,就是对请求进行分层过滤,从而过滤掉一些无效的请求。分层过滤其实就是采用“漏斗”式设计来处理请求的

假如请求分别通过 CDN、前台读系统(如商品详情系统)、后台系统(如交易系统)和数据库这几层,那么:

  1. 大部分数据和流量在用户浏览器或者 CDN 上获取,这一层能够拦截大部分数据的读取;
  2. 通过第二层(即前台系统)时数据(包括强一致性的数据)尽可能得走 Cache,过滤一些无效的请求;
  3. 再到第三层后台系统,主要作数据的二次检验,对系统作好保护和限流,这样数据量和请求就进一步减小;
  4. 最后在数据层完成数据的强一致性校验。

分层过滤的核心思想是:在不一样的层次尽量地过滤掉无效请求,让“漏斗”最末端的才是有效请求。而要达到这种效果,咱们就必须对数据作分层的校验。

分层校验的基本原则是:

  1. 将动态请求的读数据缓存(Cache)在 Web 端,过滤掉无效的数据读;
  2. 对读数据不作强一致性校验,减小由于一致性校验产生瓶颈的问题;
  3. 对写数据进行基于时间的合理分片,过滤掉过时的失效请求;
  4. 对写请求作限流保护,将超出系统承载能力的请求过滤掉;
  5. 对写数据进行强一致性校验,只保留最后有效的数据。

分层校验的目的是:在读系统中,尽可能减小因为一致性校验带来的系统瓶颈,可是尽可能将不影响性能的检查条件提早,如用户是否具备秒杀资格、商品状态是否正常、用户答题是否正确、秒杀是否已经结束、是否非法请求、营销等价物是否充足等;在写数据系统中,主要对写的数据(如“库存”)作一致性检查,最后在数据库层保证数据的最终准确性(如“库存”不能减为负数)。

高并发系统场景优化4 - 类库存扣减场景

库存场景很是典型 , 是高并发状况下对数据进行读写操做的场景

先说一下场景

总结来讲,减库存操做通常有以下几个方式:

  1. 下单减库存,即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精确的一种,下单时直接经过数据库的事务机制控制商品库存,这样必定不会出现超卖的状况。可是你要知道,有些人下完单可能并不会付款。
  2. 付款减库存,即买家下单后,并不当即减库存,而是等到有用户付款后才真正减库存,不然库存一直保留给其余买家。但由于付款时才减库存,若是并发比较高,有可能出现买家下单后付不了款的状况,由于可能商品已经被其余人买走了。
  3. 预扣库存,这种方式相对复杂一些,买家下单后,库存为其保留必定的时间(如 10 分钟),超过这个时间,库存将会自动释放,释放后其余买家就能够继续购买。在买家付款前,系统会校验该订单的库存是否还有保留:若是没有保留,则再次尝试预扣;若是库存不足(也就是预扣失败)则不容许继续付款;若是预扣成功,则完成付款并实际地减去库存。

“下单减库存”在数据一致性上,主要就是保证大并发请求时库存数据不能为负数,也就是要保证数据库中的库存字段值不能为负数,通常咱们有多种解决方案

  1. 一种是在应用程序中经过事务来判断,即保证减后库存不能为负数,不然就回滚;
  2. 直接设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时会直接执行 SQL 语句来报错
  3. 再有一种就是使用 CASE WHEN 判断语句,例如这样的 SQL 语句:UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END

秒杀减库存的极致优化

在交易环节中,“库存”是个关键数据,也是个热点数据,由于交易的各个环节中均可能涉及对库存的查询。可是,我在前面介绍分层过滤时提到过,秒杀中并不须要对库存有精确的一致性读,把库存数据放到缓存(Cache)中,能够大大提高读性能。

解决大并发读问题,能够采用 LocalCache(即在秒杀系统的单机上缓存商品相关的数据)和对数据进行分层过滤的方式,可是像减库存这种大并发写不管如何仍是避免不了,这也是秒杀场景下最为核心的一个技术难题。

所以,这里我想专门来讲一下秒杀场景下减库存的极致优化思路,包括如何在缓存中减库存以及如何在数据库中减库存。

好比使用redis , 其实咱们可使用lua脚原本保证一致性

可是使用redis 有必定的局限性

若是你的秒杀商品的减库存逻辑很是单一,好比没有复杂的 SKU 库存和总库存这种联动关系,或者多组sku同时扣减的这种不涉及复琐事务的场景,我以为彻底能够.

若是涉及到多组扣减 , 若是有比较复杂的减库存逻辑,或者须要使用事务,仍是建议必须在数据库中完成减库存-> 或者缓存支持事务

因为 MySQL 存储数据的特色,同一数据在数据库里确定是一行存储(MySQL),所以会有大量线程来竞争 InnoDB 行锁,而并发度越高时等待线程会越多,TPS(Transaction Per Second,即每秒处理的消息数)会降低,响应时间(RT)会上升,数据库的吞吐量就会严重受影响

这就可能引起一个问题,就是单个热点商品会影响整个数据库的性能, 致使 0.01% 的商品影响 99.99% 的商品的售卖,这是咱们不肯意看到的状况。一个解决思路是遵循前面介绍的原则进行隔离,把热点商品放到单独的热点库中。可是这无疑会带来维护上的麻烦,好比要作热点数据的动态迁移以及单独的数据库等

而分离热点商品到单独的数据库仍是没有解决并发锁的问题,咱们应该怎么办呢?要解决并发锁的问题,有两种办法:

  1. 应用层作排队。按照商品维度设置队列顺序执行,这样能减小同一台机器对数据库同一行记录进行操做的并发度,同时也能控制单个商品占用数据库链接的数量,防止热点商品占用太多的数据库链接。
  2. 数据库层作排队。应用层只能作到单机的排队,可是应用机器数自己不少,这种排队方式控制并发的能力仍然有限,因此若是能在数据库层作全局排队是最理想的。阿里的数据库团队开发了针对这种 MySQL 的 InnoDB 层上的补丁程序(patch),能够在数据库层上对单行记录作到并发排队。

你可能有疑问了,排队和锁竞争不都是要等待吗,有啥区别?若是熟悉 MySQL 的话,你会知道 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 就好了,以便减小对数据库的更新操做。

高并发系统场景优化4 - 兜底方案

高并发系统为了保证系统的高可用,咱们必须设计一个 Plan B 方案来兜底

高可用建设应该从哪里着手

说到系统的高可用建设,它实际上是一个系统工程,须要考虑到系统建设的各个阶段,也就是说它其实贯穿了系统建设的整个生命周期,以下图所示:

具体来讲,系统的高可用建设涉及架构阶段、编码阶段、测试阶段、发布阶段、运行阶段,以及故障发生时。接下来,咱们分别看一下。

  1. 架构阶段:架构阶段主要考虑系统的可扩展性和容错性,要避免系统出现单点问题。例如多机房单元化部署,即便某个城市的某个机房出现总体故障,仍然不会影响总体网站的运转。
  2. 编码阶段:编码最重要的是保证代码的健壮性,例如涉及远程调用问题时,要设置合理的超时退出机制,防止被其余系统拖垮,也要对调用的返回结果集有预期,防止返回的结果超出程序处理范围,最多见的作法就是对错误异常进行捕获,对没法预料的错误要有默认处理结果。
  3. 测试阶段:测试主要是保证测试用例的覆盖度,保证最坏状况发生时,咱们也有相应的处理流程。
  4. 发布阶段:发布时也有一些地方须要注意,由于发布时最容易出现错误,所以要有紧急的回滚机制。
  5. 运行阶段:运行时是系统的常态,系统大部分时间都会处于运行态,运行态最重要的是对系统的监控要准确及时,发现问题可以准确报警而且报警数据要准确详细,以便于排查问题。
  6. 故障发生:故障发生时首先最重要的就是及时止损,例如因为程序问题致使商品价格错误,那就要及时下架商品或者关闭购买连接,防止形成重大资产损失。而后就是要可以及时恢复服务,并定位缘由解决问题。

为何系统的高可用建设要放到整个生命周期中全面考虑?由于咱们在每一个环节中均可能犯错,而有些环节犯的错,你在后面是没法弥补的。例如在架构阶段,你没有消除单点问题,那么系统上线后,遇到突发流量把单点给挂了,你就只能干瞪眼,有时候想加机器都加不进去。因此高可用建设是一个系统工程,必须在每一个环节都作好。

那么针对秒杀系统,咱们重点介绍在遇到大流量时,应该从哪些方面来保障系统的稳定运行,因此更多的是看如何针对运行阶段进行处理,这就引出了接下来的内容:降级、限流和拒绝服务。

降级

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

降级方案能够这样设计:当秒杀流量达到 5w/s 时,把成交记录的获取从展现 20 条降级到只展现 5 条。“从 20 改到 5”这个操做由一个开关来实现,也就是设置一个可以从开关系统动态获取的系统参数。

这里,我给出开关系统的示意图。它分为两部分,一部分是开关控制台,它保存了开关的具体配置信息,以及具体执行开关所对应的机器列表;另外一部分是执行下发开关数据的 Agent,主要任务就是保证开关被正确执行,即便系统重启后也会生效。

执行降级无疑是在系统性能和用户体验之间选择了前者,降级后确定会影响一部分用户的体验,例如在双 11 零点时,若是优惠券系统扛不住,可能会临时降级商品详情的优惠信息展现,把有限的系统资源用在保障交易系统正确展现优惠信息上,即保障用户真正下单时的价格是正确的。因此降级的核心目标是牺牲次要的功能和用户体验来保证核心业务流程的稳定,是一个不得已而为之的举措。

限流

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

这里,我一样给出了限流系统的示意图。整体来讲,限流既能够是在客户端限流,也能够是在服务端限流。此外,限流的实现方式既要支持 URL 以及方法级别的限流,也要支持基于 QPS 和线程的限流。

首先,我之内部的系统调用为例,来分别说下客户端限流和服务端限流的优缺点。
  • 客户端限流,好处能够限制请求的发出,经过减小发出无用请求从而减小对系统的消耗。缺点就是当客户端比较分散时,无法设置合理的限流阈值:若是阈值设的过小,会致使服务端没有达到瓶颈时客户端已经被限制;而若是设的太大,则起不到限制的做用。
  • 服务端限流,好处是能够根据服务端的性能设置合理的阈值,而缺点就是被限制的请求都是无效的请求,处理这些无效的请求自己也会消耗服务器资源。

在限流的实现手段上来说,基于 QPS 和线程数的限流应用最多,最大 QPS 很容易经过压测提早获取,例如咱们的系统最高支持 1w QPS 时,能够设置 8000 来进行限流保护。线程数限流在客户端比较有效,例如在远程调用时咱们设置链接池的线程数,超出这个并发线程请求,就将线程进行排队或者直接超时丢弃。

限流无疑会影响用户的正常请求,因此必然会致使一部分用户请求失败,所以在系统处理这种异常时必定要设置超时时间,防止因被限流的请求不能 fast fail(快速失败)而拖垮系统。

拒绝服务

若是限流还不能解决问题,最后一招就是直接拒绝服务了。

当系统负载达到必定阈值时,例如 CPU 使用率达到 90% 或者系统 load 值达到 2*CPU 核数时,系统直接拒绝全部请求,这种方式是最暴力但也最有效的系统保护方式。例如秒杀系统,咱们在以下几个环节设计过载保护:

在最前端的 Nginx 上设置过载保护,当机器负载达到某个值时直接拒绝
HTTP 请求并返回 503 错误码,在 Java 层一样也能够设计过载保护。

拒绝服务能够说是一种不得已的兜底方案,用以防止最坏状况发生,防止因把服务器压跨而长时间完全没法提供服务。像这种系统过载保护虽然在过载时没法提供服务,可是系统仍然能够运做,当负载降低时又很容易恢复,因此每一个系统和每一个环节都应该设置这个兜底方案,对系统作最坏状况下的保护。

最后,以java系统为例 , 如何提升系统性能

咱们讨论的主要是系统服务端性能,通常用 QPS(Query Per Second,每秒请求数)来衡量,还有一个影响和 QPS 也息息相关,那就是响应时间(Response Time,RT),它能够理解为服务器处理响应的耗时
正常状况下响应时间(RT)越短,一秒钟处理的请求数(QPS)天然也就会越多,这在单线程处理的状况下看起来是线性的关系,即咱们只要把每一个请求的响应时间降到最低,那么性能就会最高。
可是你可能想到响应时间总有一个极限,不可能无限降低,因此又出现了另一个维度,即经过多线程,来处理请求。这样理论上就变成了“总 QPS =(1000ms / 响应时间)× 线程数量”,这样性能就和两个因素相关了,一个是一次响应的服务端耗时,一个是处理请求的线程数。

响应时间和QPS的关系

对于大部分的 Web 系统而言,响应时间通常都是由 CPU 执行时间和线程等待时间(好比 RPC、IO 等待、Sleep、Wait 等)组成,即服务器在处理一个请求时,一部分是 CPU 自己在作运算,还有一部分是在各类等待。

理解了服务器处理请求的逻辑,估计你会说为何咱们不去减小这种等待时间。很遗憾,根据咱们实际的测试发现,减小线程等待时间对提高性能的影响没有咱们想象得那么大,它并非线性的提高关系,这点在不少代理服务器(Proxy)上能够作验证。

若是代理服务器自己没有 CPU 消耗,咱们在每次给代理服务器代理的请求加个延时,即增长响应时间,可是这对代理服务器自己的吞吐量并无多大的影响,由于代理服务器自己的资源并无被消耗,能够经过增长代理服务器的处理线程数,来弥补响应时间对代理服务器的 QPS 的影响。

其实,真正对性能有影响的是 CPU 的执行时间。这也很好理解,由于 CPU 的执行真正消耗了服务器的资源。通过实际的测试,若是减小 CPU 一半的执行时间,就能够增长一倍的 QPS。

也就是说,咱们应该致力于减小 CPU 的执行时间。

线程数对 QPS 的影响

单看“总 QPS”的计算公式,你会以为线程数越多 QPS 也就会越高,但这会一直正确吗?显然不是,线程数不是越多越好,由于线程自己也消耗资源,也受到其余因素的制约。例如,线程越多系统的线程切换成本就会越高,并且每一个线程也都会耗费必定内存。

那么,设置什么样的线程数最合理呢?其实不少多线程的场景都有一个默认配置,即“线程数 = 2 * CPU 核数 + 1”。除去这个配置,还有一个根据最佳实践得出来的公式:

线程数 = [(线程等待时间 + 线程 CPU 时间) / 线程 CPU 时间] × CPU 数量 => 这个公式的核心思想就行将等待的时间让给其余线程去处理

固然,最好的办法是经过性能测试来发现最佳的线程数。

如何优化系统

对 Java 系统来讲,能够优化的地方不少,这里我重点说一下比较有效的几种手段,供你参考,它们是:减小编码、减小序列化。接下来,咱们分别来看一下。

1. 减小编码

Java 的编码运行比较慢,这是 Java 的一大硬伤。在不少场景下,只要涉及字符串的操做(如输入输出操做、I/O 操做)都比较耗 CPU 资源,无论它是磁盘 I/O 仍是网络 I/O,由于都须要将字符转换成字节,而这个转换必须编码。

每一个字符的编码都须要查表,而这种查表的操做很是耗资源,因此减小字符到字节或者相反的转换、减小字符编码会很是有成效。减小编码就能够大大提高性能。

那么如何才能减小编码呢?例如,网页输出是能够直接进行流输出的,即用 resp.getOutputStream() 函数写数据,把一些静态的数据提早转化成字节,等到真正往外写的时候再直接用 OutputStream() 函数写,就能够减小静态数据的编码转换。好比 把静态的字符串提早编码成字节并缓存,而后直接输出字节内容到页面,从而大大减小编码的性能消耗的,网页输出的性能比没有提早进行字符到字节转换时提高了 30% 左右。

2. 减小序列化

序列化也是 Java 性能的一大天敌,减小 Java 中的序列化操做也能大大提高性能。又由于序列化每每是和编码同时发生的,因此减小序列化也就减小了编码。

序列化大部分是在 RPC 中发生的,所以避免或者减小 RPC 就能够减小序列化,固然当前的序列化协议也已经作了不少优化来提高性能。有一种新的方案,就是能够将多个关联性比较强的应用进行“合并部署”,而减小不一样应用之间的 RPC 也能够减小序列化的消耗。

所谓“合并部署”,就是把两个本来在不一样机器上的不一样应用合并部署到一台机器上,固然不只仅是部署在一台机器上,还要在同一个 Tomcat 容器中,且不能走本机的 Socket,这样才能避免序列化的产生。

相关文章
相关标签/搜索