Everything You Know About Latency Is Wrong

Everything You Know About Latency Is Wrong

December 12, 2015 by Tyler Treat

雨钓 译 有增改

前言 

Okay,或许并不是你知道的所有关于延迟的认识都是错误的,但是现在我需要提醒你的是,我们推理延迟的方法和工具存在严重的缺陷,本位中我会解释为什么这样说。事实上,他们不仅仅是有缺陷的,甚至是在误导你。

当我在九月份去 Strange Loop的时候,我参加了Gil Tene分享的一个名叫“ Understanding Latency and Application Responsiveness”的研讨会,Gil Tene是Azul System的CTO,最著名的是它的 C4 pauseless garbage collector 和相关的 Zing Java runtime。研讨会持续了4个半小时,Gil还做了一个40分钟的演讲: “How NOT to Measure Latency” ,这是一个基本的简略版,如果你有机会听Gil的演讲或者参加他的研讨会,我建议你一定要参加。或者你可以在网上找找他的演讲或者相关的幻灯片。

本文的其余部分主要是对这个演讲的总结,你可能从谈话中得不到任何东西,但是我认为以书面的方式展示有助于吸收一些关于这个观点的更多的东西。另外我之所以把它写下来,也是出于自身的利益考虑:这有助于我加强理解和记忆。

What is Latency?

延迟(Latency)通常被定义为:“一次操作发生所花费的时间”。这就意味着每一个操作都有他自己的延迟——一百万个操作就有一百万个延迟。这就导致延迟不能用 units/time来度量,我们感兴趣的是延迟的行为。为了使表达更有意义,我们必须描述延迟的完整分布,但是延迟几乎从不遵从正态分布、高斯分布、或泊松分布,因此,中值、均值、标准差对于它而言都是无效的。

延迟往往具有多种模式,这在一定程度上归因于在响应时间上的“打嗝”( hiccups), hiccups类似于系统周期性的冻结,可能有多种原因导致,GC、hypervisor 暂停、上下文切换、中断、数据库重建索引、缓冲刷新到磁盘等等。

这些 hiccups 从来都不像正态分布,并且如下图所示,模式间的转移通常是快速且广泛的。

那我们如何有效的描述延迟的分布呢?那就是我们必须查看百分位数值( percentiles),然而它更加微妙。许多人陷入了一个陷阱,那就是只专注于普通情况。问题是,延迟的行为比普通情况多得多。不仅如此,所谓的常见的情况也并非是你所认为的那样常见。

这在一定程度上源于工具的问题。目前许多我们使用的工具都不能很好的捕获和表示这些数据。例如,大多数Grafana生成的延迟图,正如下面的这个图,基本是没有价值的。我们喜欢看漂亮的图表,通过一些方便的绘图工具我们可以绘制一些可读性很强的彩色图表,当你想要隐藏一些不好的情景时,只看第95百分位数(通常用p95表示)的值就可以。正如Gil所描述的:这是一个营销系统,不管是CTO,潜在客户,抑或是工程师,总有人受骗( someone’s getting duped.)。此外,平均百分位数从数学角度看是荒谬的,为了节省空间,我们经常保留摘要而丢弃数据,但是“第95 百分位数的平均值”是一个毫无意义的命题。正如你已经注意到的大多数在Grafana图标中的标签,你不能平均百分位数(  You cannot average percentiles,)不幸的是实际情况比这更糟。

Gil说“你永远都不应该去掉的第一个指标是最大值,它不是噪声,而是信号,剩下的才是噪声”(注:数字信号处理中,噪声通常是指对正常分析信号造成干扰的无用的信息。这里指无用的数据)。针对着一点,研讨会上很多人回应到:“如果最大值是由于类似VM重启时的情况导致的,那会怎么样?这不能描述系统的行为,因为它发生的几率太小了,是几乎不太可能发生的事情”。通过忽略最大值,你可以有底气的说“那没有发生过”。如果你能找到产生噪声的原因,那么这样做(忽略最大值)没有问题。反之,如果你没有捕获这些数据,你将不知道实际发生了什么。

How Many Nines?

但是我到底需要多少个9呢?根据定义,第99百分位数(简称p99)是百分之99的观测值都小于这个延迟值。第99百分位数(p99)罕见吗?如果我们有一个搜索引擎节点,一个键值存储节点,一个数据库节点,或者一个CDN节点,达到p99的概率是多大?

Gil描述了他收集到的一些真实的数据 ,这些数据显示了我们实际访问的WEB页面达到99百分位数(p99)的情况(一次完整的服务包括了多次请求,即使这些子请求是并发的发送、处理的,但对于最终用户而言仍需要等待最慢的那个调用完成),第二列显示的是单次访问WEB页面的HTTP请求次数。第三列显示一次访问经历p99的可能性.除了google.com每个页面都有大于50%的可能性看到p99。

Gild的观点是,p99是大多数page都将看到的,并非少数。

什么指标更能代表用户体验呢?我们已经确定不会是平均值,也不是中位数,那是否可能是99百分位数(p99)?或者99.9百分位数(p999)?Gil给了一个简单地的示例:一个典型的用户会话包括5个页面加载,每个页面平均包含40个资源。有多少用户不会遇到比p95更糟糕的情况呢?答案是0.003%。通过查看p95,你将看到这个值(即p95)只与0.003%的用户相关,这就意味着99.997%的用户会经历比这个数字更糟糕的延迟,那你为什么还要看它呢(指p95)?反过来说,18%的用户会经历比p99.9百分位数(p999)更糟糕的响应时间,意味着82%的用户将体验到p999更好的体验。更进一步,超过95%的用户将体验到99.97百分位数(p9997),并且多于99%的用户将体验99.995百分位数(p99995)。中位数是 这样一个值,其中99.9999999999% 的响应时间比中位数的值更糟,这就是为什中位数是没有任何意义的。人们通常用中位数来描述典型的响应时间,但是实际的情况只会比中位数描述的情景更加糟糕,但中位数仍是最常用的度量。

如果我们观察大量的9如此重要(确实如此),那么为什么大多数的监控系统仅在p95和p99就停止了。答案很简单:因为越多的9,越难实现。多数监控系统手机的数据是统计5s或者10s的时间窗口内的数据。此外还有一个事实是:我们不能求对百分位数求平均值,也无法从一堆百分位数小样本中推导出5个9的百分位数,这也就意味着无法知道一分钟或一小时的99.999百分位数(p99999)是多少。我们最终丢弃了大量好的数据,并失去了保真度。

A Coordinated Conspiracy

Benchmarking is hard. 基准测试是困难的,大多数针对延迟的基准测试都是有缺陷的,因为大多的基准测试工具本身就是不完整的。基准测试出现问题的首要原因就是:“ coordinated omission”,Gil称其为“我们都参与的阴谋”,因为它无处不在。几乎所有的负载生成器( load generators)都有这个问题。

我们可以看一个常见的负载用例,以便于我们通过用例了解这个问题是如何表现的。测试中,客户端通常以一定的速率发出请求,测量每个请求的响应时间,并对结果进行存储,以便于之后计算百分位数。

问题是,如果被测试的内容花费的时间比相邻两个请求发送的时间间隔更长,那会怎样?例如你每一秒发送一个请求内容,但是这个请求的处理需要花费1.5秒呢?你发送下一个请求前需要等待,但是如果你真的采用等待一段时间再发送下一个请求的处理方式,通过这样做你避免了对系统发生故障时的测量。你已经通过避开或者干脆不测量坏事情发生时的情况来进行协调。为了保持准确这种测量方式只有在所有响应符合预期的时间间隔时才有效。

在监控代码中也会出现协调遗漏(coordinated omission)。我们通常的测量方式是在执行之前记录时间戳,紧接着运行要检测的程序,最后记录运行完成时的时间戳,最后分析这个数据。我们将数据存储并计算百分位数。下面的代码取自Gassandra基准测试:

但是如果系统遇到前面描述的“打嗝”(hiccups)之一,你将有一个错误操作和其他一万个在排队的操作。当这10000个事件被处理的时候,情况看起来很好,但实际上很糟糕。长操作只被测量一次(而时间窗口是有限的,如上所说通常是5s 或者10s),时间窗口之外的延迟不会被测量到。

在这两个例子中,我们都选择性的省略了不好的数据,而这将会对基准测试造成多大的影响呢?如下图所示,它的影响是巨大。

首先来看服务端的服务时间)想象一个完美的系统,每秒处理100个请求,每个请求正好花费1ms,现在考虑一种情况:我们在100s的完美操作之后冻结系统100s(例如使用CTRL+Z)并且重复操作,我们可以直观的描述这个系统。

  • 前100秒的平均值是1ms

  • 之后100秒的平均值是50s (开始和结束时各一个)

  • 在 200秒内的平均值是25秒。

  • 第50分位数是1ms

  • 第75分位数是50ms

  • 第99.99分位数是100秒。

(现在从客户端测试请求响应时间)现在我们尝试使用负载生成器来测试系统。在冻结之前,我们运行100秒,每秒100个请求,共10000个请求,每个请求耗时1ms。结束后我们得到100秒的结果。这还是我们所有的数据,当我们计算后我们得到以下数据。

  • 200秒的平均值是10.9ms(服务端的测试是25秒)

  • 50分位数是1ms

  • 75分位数是1ms(服务端的测试是50秒)

  • 99.99分位数是1ms(服务端的测试是100秒)

至此,基本上你的负载生成器和监控代码告诉你,你的系统可以准备投入生产环境了,但事实上你的测试欺骗了你。一个简单地CTRL+Z的测试可以捕捉到协同遗漏(coordinated omission),但是人们很少这样做。以这种方式校准系统非常重要,如果它给你这样的结果,那么请扔掉所有的数据,他们毫无价值。

你必须以随机和公平的几率来测量,如果你在100秒内测量10000个事情,你必须在拖延的第二个100秒时间内测量10000个事情。如果你这样做了,你将会得到正确的数据,但是他们并不那么漂亮,协调遗漏是一种简单地忽略、删除最终遗漏所有不好的东西的方式,但是数据是好的。

那是否这些数据仍然是有用的?即使它们并不能准确的表示系统,我们仍然可以使用它来识别性能回归或者验证改进?可悲的是事实上并非如此。看看为什么:想想一下我们改进了我们的系统。不是在完美操作100秒后冻结100秒,它在100秒后以每5ms处理一个请求的速率处理所有请求。现在来计算下:

  • 50百分位数是1ms

  • 75百分位数是2.5ms(采用冻结时显示的是1ms)

  • 99.99百分位数是5ms(采用冻结时显示的是1ms)

这个数据告诉我们,我们玩坏了4个9,并且使得系统更糟。这告诉我们,我们需要恢复到以前的那个方式(即采用冻结的方式,因为采用冻结时测试得到的指标更好),这会是一个错误的决定。因为存在糟糕的数据,使得原本应该更好的系统,看起来反而更糟糕。这表明,你不能依靠这些数字给你的直觉来做判定,因为这是数据是垃圾。

对于大多数据负载生成器而言,实际情况要比这糟糕的多。这些系统的工作原理是产生恒定的负载,如果我们的测试每秒生成100个请求,我们在第一个100秒里运行10000个请求。当我们冻结系统时,我们只处理一个请求。在系统解冻之后负载生成器看到后面的9999个请求,发送这些请求并进行跟踪。这样做导致的结果就是,他不仅忽略了糟糕的请求,还用好请求替换了他们。现在数据的错误是丢弃坏请求的两倍。

协调遗漏(coordinated omission)真正遗漏的是服务时间,而不是响应时间(服务时间和响应时间是不一样的,响应时间是客户端看到的,除了处理请求的时间(服务时间service time)还包括来回网络延迟和各种排队延迟)。如果我们想象顾客给客服打电话,服务时间就是客服服务该客户所花费的时间。响应时间是客户等待直到有客服接听的时间加上客服为他服务的时间。如果到达率高于服务率,响应时间将继续增长,因为“打嗝”( hiccups)或其他情况可能发生(例如网络延迟等等),所以响应时间会来回跳动。 但是,由于协调遗漏(coordinated omission)的存在,通过实际测试得到的数据,对你隐瞒了服务时间,并隐藏了系统冻结或请求排队的事实,从而导致你基于这些数据得到的响应时间是错误的,会误导你。

Measuring Latency

延迟并不是孤立的( Latency doesn’t live in a vacuum.)。测量响应时间很重要,但是您需要在负载上下文中查看它。 但我们如何正确地保证这一点呢?当系统几乎无所事事,压力很小的时候,事情几乎是完美的,所以这样系统显然不是很高效。当你的系统全速运行高度负载时,事情将会崩溃。这在某种程度上是有用的,因为它告诉我们在我们开始接触到系统瓶颈之前我们系统能走多“快”。

然而,研究饱和状态下的延迟行为就像在将汽车保险杠绕在一根杆子上之后观察它的形状一样。 当你撞到临界点的时候,唯一重要的是你撞到了临界点。以此试图设计一个更好的保险杠是没有意义的,但是我们可以设计并限定系统失去控制时的速度。在饱和状态下,一切都会变得很糟糕,所以观察在确定的操作范围之外的情况并不是特别有用。

更重要的是测试空转和达到临界值之间的速度。定义SLA(服务质量协议:Servie Level Agreements。常与之对应的还有SLO:Service Level Objectives即服务质量目标)并绘制这些需求(通常一份SLA合约中会明确声明相应中位数小于200ms,99%请求的响应时间小于1s,且要求至少99.9%的时间都要达到上述服务指标,以明确服务质量预期,并允许客户在不符合SLA的情况下要求赔偿),然后使用不同的负载和不同的配置运行不同的场景。这告诉我们是否满足SLA,以及需要提供多少台机器。如果不这样做,就不知道需要多少台机器。

我们如何捕捉这些数据?在理想的情况下,我们可以存储每个请求的信息(更具体的是在时间窗口内保留所有请求的响应时间列表,每分钟做一次排序),但这通常是不切实际的(因为效率太低,所以通常采用一些近似的算法,如正向衰减t-digest和HdrHistogram,本文作者推荐的就是HdrHistogram)。HdrHistogram是一个工具,它允许你捕获延迟并保持高分辨率。它还包括用于纠正协调遗漏和绘制延迟分布的工具。HdrHistogram的原始版本是用Java编写的,但是还有许多其他语言的版本。

To Summarize

要理解延迟,必须考虑整体分布。通过绘制延迟分布曲线来实现这一点。仅仅看第95百分位(p95)甚至第99百分位数(p99)是不够的。长尾效应会使得情况更糟糕,中位数并不能代表“常见”情况,平均值更是如此。没有定义操作延迟的单一度量。注意监视和基准测试工具及其报告的数据。但请注意你不能平均百分位数。

记住延迟不是服务时间。如果在绘制数据时省略了协调遗漏的部分,那么曲线中通常会出现一个快速、高的上升。运行“CTRL+Z”测试,看看是否有这个问题。非遗漏测试的曲线要平滑得多。很少有工具能够真正纠正协调遗漏。

延迟需要在负载上下文中进行测量,但是在每次测试中不断地让您的汽车撞上杆子是没有用的。这不是你在生产环境中运行的方式,如果是,您可能需要提供更多的机器。使用它来确定你的极限,并测试中间的可持续吞吐量,以确定是否满足SLA。目前有很多有缺陷的工具,但HdrHistogram是少数没有缺陷的工具之一。它对于基准测试非常有用,而且直方图是可添加的,而HdrHistogram使用 log bucket,因此它对于在生产中捕获 high-volume数据也非常有用。

版权声明:本文为博主原创文章,首发公众号,blog同步更新,欢迎转载,转载请注明出处。https://blog.csdn.net/u012802702/article/details/86421171 公众号地址:

猜你喜欢

转载自blog.csdn.net/u012802702/article/details/86421171