为什么你的服务会变慢?

云栖号资讯:【点击查看更多行业资讯】
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!

你开发了一个服务,调用它,它做了一些事情并返回结果。那么,它需要花多长时间?为什么有时候它花的时间比用户期望的要长?在这篇文章中,我将从最B a =基础的讲起,然后逐步介绍一些标准的术语,同时着重强调一些需要/ * | z c K @知道的关键点。

首先,我们需要一种方式^ I w O c A来度量时长,还需要理解两个完全不同的度量角度。从调用服务的外部用户角度来看,我们需要度量响应A n ! 9 z t时间。从服务处理请求的角度来看,我们需要度量服务时间。这就引出了第一个关键点,人们常常分不* 3 S t 2 Z清一些指标。

对于用户来说是响应时间(Response T K Y ] r u Z l sime),对于服务来说是服务时间(Service Time)。

在真实世界里,每一个处理过程都包含了很多步骤,每个步骤都需要占用一些时间。步骤占用的时间叫作驻留时间(Residence Time),驻留时间由等待时间(Wait Time)和服Q i ; ;务时间组成。以用户登录 App 为例,一个用户登录手机 App,App 会调用 Web 服务进行用户认证。为什么有时候会很慢?按理说A E K ;,每一次手机上生成请求的时间、将请求传输给 We` Z [ X R S qb 服务的时间、查询用户的时间、F F w P W s返回结果并显示下一个屏幕的时间应该是一样M + 8 c B g的。造成响应时间长短不一的是排队时间,也就是等待正在处理其他请求的资源。从手机到认证服务器之间的网络传输需要经过很多跳,f 7 } J 0 5每一跳前面都有等待被发送J K ` H T 8的数据包。如果队列是空的或者队列很短,那么响应速度就很快,如果队列很长1 m u { b 6 ? :,响应就很慢。当请求达到服务器时,也需要排队等待 CPU 处理。如果需要查询数据库,还需要排到另一个队列里。

排队等待是导致响* E / g p应时间增加的主要原因。

监控工具会提供一个叫作吞吐量(Throughput)的指标,用来度量处理频度。在某些情况下,我们也会得到一个叫作到达率(Arrival Rate)的指标,用于度量到达服) h 1 t $ l $ i B务器的请求的速率。在理想情况下,比如一个具有稳定工作负载状态的 Web 服务,一个请求对应一个响应,那么吞吐量和到达率是一样的。不过,重试和错误会导致到达率增加,但吞吐量不会增加。对于快速变化的工作负载或者需要长时间处理的请求(比如批次作| 1 G X # O业)r X | 6 8 i R,会出现到达率和吞吐量之间的不? j Q }均衡,并产生更为复杂的请求模式。

吞吐量是指已经成功8 X +处理完的请求数量,. ! * p U它跟达到率是不一样的。

我们可以通过一些跟踪系统(比如 ZipkiO 3 r g Z F -n 或 AWS X-Ray)来跟踪单个请求流,不过我们这里讨论的是大量请求以及它们之间的交互关系。我们通过固定的时间间隔来度量均值,时间间隔可以是秒、分钟、小时或天。计算均值需要足够多的数据,一G ) N z d般来说每个均值至少需要 20 个数e * [ P据点。

如果请求不是很频] h / a v繁,请选择一个至少包含 20 个请求的时间间隔,这样才有可能得到比较有用的信息。

如果选择的时间间隔太大,会导致工作负载的变化被隐藏掉。例如,对于视频会议系统来说,大部分会话会在一个小时的头一分钟左右启动,并且很容易在这些时间段达到峰值,让系统发生过载,如果时间间隔是小时,这些信息就会丢失掉。所以,对于这种情况,时间间隔设为秒更为恰当。v ^ T @

对于变化快的工作负载,可以使用秒级的均值。

监控工具各种各样,但一般很少会直接告诉我们Z U 5 P等待队列有多长或有多少并发度可用来处理队列。大多数网络每次5 w } t C d q R %只传输一u I Z 6个数据包,但 CPU 的每个核心或 vCPU 可以并行处理队列里的任务。数据库通常有一个固定的最大连接数,用来限制并发度。

对于处理请求的I [ B 0 f @ c每一个步骤,_ S T m ; , 0 # f可以记录或估计用于处理请求的并发度。

如果系I 4 V 4 * / T Z 统运行稳定,有稳定的平均吞吐量和响应时间,那就很容易估算等待队列的长度,只需要将吞吐量和驻留时间相乘即可。这就是所谓的利特尔法则法则(Little’s Law)。这个法则很简单,监控工具经常用它来估算队列长度,但它只对具有稳定均值的系统有效。

根据利特尔法则,平均队列长度 = 平均吞吐量 * 平均驻留时间。

为了更好地理解这个法则的原理,我们需要知道请求是如何1 - g _到达服务器以及请求之间的间隔是怎样h Z 4 2的。如果我们通过循环进行简单的性能测试,请求之间的间隔是固定的,那么利特尔法则b G X ) E P就无效,因为这样出现的队列很短,而且这样的测试不真实。我们通常会进行这样的测试,以为很完美,但是在将服务部署到生产环境之后,眼睁睁地看着它越跑越慢,吞吐量越来越低。

这种速率固定的循环测试不会有队列出现,它们只是在模拟传送带。

在真实的网络世界n M ? ;中,用户都是独立的,他们发送自己的请求,不同用户发送的请求之间的间隔是随机的。t = V D S R D @! l i以,在测试时,我们需要使用可以生成具有随机等待时间的请求的生成器。大多数系统会使用随机分布,虽然比模拟传送带要好,但也X u 9 H # d $ 0是不对的。要模拟真实的网络流量,并让利特尔法则生效,我们需要使用负指数分布(Negative Exponential Distribution)。Nei$ X A y X i !l Cunther3 Y 4 N 博士在这篇文章中解释了u * ) }什么是负指数分布。

要生成更加真实的队列} * R E,需要使用恰当的随机时间算法。

但问题是,真实的t & B | ! q 7 i w网络流量并不是随机分布的,而是带有爆发性质的。想象一下,当一个用户打开一个手机 App,它不会只发出一个请求,而是很多个。在8 o + ?网络购物抢购活动中,会有很多用户同时打开 App,这会导致流量爆s 3 2 5 N a u j @发。o $ 1这种分布形态叫作帕累托或双曲线。另外,当网络经过重新配置,流量会被延迟,就会出现队列,而队列J { [ N :会给下游系统带来闪电式的冲击。JimW ^ & n y m ) % Brady 和 N[ } ? ] ^ K } Y ^eil Gunthe7 L W p br 写了一些脚本,演示如何配置测试工具,从而获得更加真实? A H D P ( T的流量。Jim Brady 还写了一篇关于如何知道负载测试好坏的论文。

相比常用测试工具默认生成的流量负载W } J 9 X 7 n r,真实世界的流量负载更具爆发性,会导致更长的等待队列和响应时间。

等待队列和响应时间应该是变化的,而且即使是在使用率很低的时候也会出现一些很F p b 2 d A H慢的请求处理c m a z速度。那0 I g { G l - #么,g o V W . C当处理步骤中的某一步开始变慢时会怎样/ 3?当使用率增加,一些处理步骤没有足够的可用资源(比如网络传输),那么请求相互争夺资源的情况就会增加,驻留时间也会增加。一般来说,当使用率达到 50% 到 70% 时,网络就会逐B ! % . & g )渐变慢。

将网络使用率保持! 7 / w在 50% 以下D n d v R i n V可用获得更好的延k ] )时。

对于并8 r ` & A U m 2行度高的 CPU,在使用率较高的情况下,速度会变得更慢,影w ` . a响也更大,大到令你吃惊。如果你将最后一个可用的 CPU 看作争用点,那么这就很直观了。例如,如果有 16 个 vCPU,最后可用的 CPU 具有 6.25% 的处理能力,那么使用率就是 93.75%。对于具有 100 个 vCPU 的系统,它的使用率约为 99%。在? G V z W稳定状态下,公式 R=S/(1-} e IU^N) 可用来近似估算随机到达服务器的请求的行为。

在多处理器系统中,随着使用率的增加,平均驻留时间的膨胀会减少,但强度% F v却增加了。

使用率使用比例O B f g,而不是F 8 F d U百分比,并将其作为处理器核数的幂底数。用 1 减掉使用率的 N 次幂,再用平均服务时间除以结果,就可以估算出平均驻留时间。如# * b A T ? &果使用C g z ] ) [ 7率很低,平均驻留时间就会很接近平均服务时间。如果一个网络的 N=1 并且使用率为 70%,那么用平均服务时间除以 0.3,得到的平均驻留时间就是低使用率时的h [ Y z q三倍。

通常情况下,我们需要将平均驻留时间保持在 2 到 3 倍b @ % 8 !以下,这样才能获得更短的用户响应时间。

对于一个有 16 个 vCPU 并且使用率为 95% 的系统,0.95^16=0.44,再用平均服务时间除以 0.56,就会得到两倍的平均驻留时间。如果使用率为 98%,那么 0.98^16=0.72,再用平K 1 L ? T均服务时间除以 0.28,平均驻留时间就会变慢,变得不可接受,而此时使用率仅增加了 3%。

当多处理器系统的使用率F @ D Q [ a Z u 7很高时,一个很小的负载d ` c V变化就会产生很大的影响,这是多处理器系统的一个问题。

Uu p Enix/Linux 系统有一个指标叫作负载平均(Load Average),人们通常对它了解得不够透彻,它存在一些问题。Unix 系统(包括 Solaris、AIX、HPUX)会记r / l : G f r录运行中的和等待 CPU 的线^ B J J }程数,Linux 还会记录等待 IO 阻塞的线程数,然后还O C V N W z _ *使用了三种时间衰减值,分别是 1 分钟、5 分钟和 15 分钟。~ 3 - I A首先我们需要知道的是,这个指标可以追溯到 60 年代的单核 CPU 时代,所以我通常会将负载平均值除以 vCP4 O S L r O jU 的数量,从而得到具有可比性的值。其次,这个指标与其他指标不一样,它没有使用固定的时7 4 = 3 m A g ; a间间隔,所以它们不属于同一b v P o d z $ 种平C D 4 ( l o q j s均值。第三,这个指标在 Lin$ G P (ux 上的实现已经成了一个 bug,被制度化成一个系统特性,其结果也被夸大了。

负载平均这个指标不度量负载,也不是均值,所以x b i最好把它忽略掉。

如果一个系统超载,请求达到的速度超过了处理能力,使用率就达到了 100%,那么上面的那个公式4 p V ? ! O S 3 +的除数就是 0,这样会导致驻留时间无穷大。在实际当中,这种情况会更加糟糕,因为当系统变慢时,上游的用户会发送重试请求,这样会加大系统t { W ; 1 h G E的负载,出现“重试风暴”。这个时候,系统就会出现很长的等待队列,无法做出响应。

当系统使用率达到 100% 时K W ! X L f } V d就会出现很长的等待队列,无法对请求做出响应。

我发现系统的重试次数通常被配置得很大,超时^ , % 5 k 4 ^ U被配置得很长,这样会增加工作负载,更有可能出现重n 6 & x - [试风暴。之前我有深入地探讨过这个问题,后来又新写了一篇文章。更好的做法是使用较短的超时时间和单次重试,如果有其他可用实例,最好把请求U 3 J U a h Z发给它们。

系统的重试时间不能全部设置= u P 5 R成一样,前端部分应该设置得长一些,后端部分应该设置得短一些。

一般情况下,人们会通过重启来清楚超载的队列,但一个经过精心设计的系统会限制队列的长度,并通过丢弃请求或做出“快速失败”的响应来削减流量。数据库和其他具有固定连接数限制的服务都使用了这种方} 7 R k式。当你J Z P A M N 8 H无法获得可用的请求能力就会得到一个快速失败的响应。如果连接数限制设置得太低,数据库会拒绝它本该有K T O ` 9 [能力处理的请求,, V C c K如果设置得太高,数据库在拒绝更多请求之前就已经变慢了。

要多想想当系统使用率达到 100% 时该怎么办,以及如何设b Z 6 ,置恰当的限制连接数。

要想在极端情况下还能保持较好的响应速度,最好的方式是使用快速失败响应和削减流量。真实世界中的大部分系统即使是在正常情况下也会出现很多慢响应。不过,我们可以通过正确的指标监控来解决这些问题,对系统进行精心的设计和测试,这样就有可能构建出具有最大化响应速度的系统。

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live

立即加入f _ N v 9 ] S M社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-, E r 206-10
本文作者:Adrian Cockcroft
本文来自:“infoq”,了解相关信息可以关注“infoq”