在今天的帖子中,我们将讨论常见分布式系统架构中的延迟:根叶或父子扇出架构。我们将试图直观地了解是什么驱动了尾部延迟,推导出将父延迟和子延迟分布结合在一起的公式,并提供有用的交互式可视化来帮助理解这些系统中的尾部延迟。
在处理分布式系统时,常见的模式是有一个根聚合器节点将请求扇出到多个并行工作的叶节点,然后将它们的响应发送回根节点以聚合并创建最终响应。通常,系统的API是通过根聚合器节点公开的,所以这些根节点的延迟才是最重要的。
作为经验丰富的分布式系统开发人员,我们知道延迟(和性能)是一个形状,而不是一个数字。这意味着我们不应该只关心服务的平均延迟是多少-我们关心完整的延迟分布-至少我们应该关心尾部。人们通常专注于监控$90^th}$或$99^th}$百分位数(p90或p99),并可能使用热图来可视化整个延迟分布。
在我们的父聚合器和许多子节点设置的情况下,如果我们要关注它的p99延迟,那么很想知道对于给定的扇出,我们的子节点必须多快才能获得特定的p99父延迟。这在调试时也很有用,但反之亦然。如果我看到父节点的p95延迟峰值,我需要关心子节点上的多大百分比延迟?这种情况如何随扇出大小变化?
在我们进一步分析问题之前,我们应该首先准确地定义我们正在分析的问题。在最简单的形式中,父子系统有两个定义属性:
请求的父等待时间大于或等于所有子等待时间的最大值,因为父请求必须等待所有子响应。
这两个特性意味着,当我们分散给更多的孩子时,我们的父母潜伏期将会变得更糟,因为我们更有可能遇到长尾潜伏期事件。相反,如果我们能够减少扇出或降低子级延迟的差异,我们将能够降低总体延迟。
不过,目前还不明显的是,减少扇出或改善儿童潜伏期差异的影响有多大。为了尝试了解一些情况,让我们考虑这样一种情况,其中孩子的延迟服从对数正态分布,其中$\µ=3$,$\sigma=0.2$,并考虑该孩子的延迟分布,以及每个请求分散到不同数量的孩子的父母:10,100,1000。2现在,我们将根据来自生成的数据的观测,为孩子和父母可视化重建的概率密度函数:
显而易见的是,随着扇出的增加,父延迟分布变得越来越差,并且受子延迟的尾部延迟控制。当我们分散到1000个孩子时,父延迟分布看起来几乎与子延迟分布完全无关。虽然我们可以预测到这一点,但希望这种可视化有助于建立我们对问题的直觉。
我们开始这场冒险试图回答一个问题,现在是时候回到这个问题上了:哪个子延迟百分位数驱动给定的父延迟百分位数?
虽然问题很简单,但我一直在努力解决这个问题,直到一位朋友指出,可以将百分位延迟看作不是描述延迟分布中的一个点,而是一个请求在给定的时间内完成的概率。在此之前,我一直在努力解决这个问题,直到一位朋友指出,百分位延迟不是描述延迟分布中的一个点,而是一个请求在给定时间内完成的概率。因此,如果p90延迟是10ms,我们可以将其重新定义为请求在10ms内完成的概率是$90\$。
当我们将这一洞察力与父请求仅在其所有子请求完成后才完成这一事实相结合时,我们就可以推导出问题的封闭形式解决方案。
由于父请求仅在所有$N$个子请求完成后才完成,因此父请求在时间$t$内完成的概率是所有$N$个子请求在时间$t$内完成的概率。假设所有子延迟是独立且相同分布的3,则我们可以计算父概率为:
既然我们已经有了解决问题的公式,我们就可以开始检查一些专家了。如果我们关心父代的$90^th$百分位数延迟,并且扇出到2个子代,那么我们需要关心子代的$94.86^th}$百分位数,因为$\sqrt{0.9}\大约0.9486$。
关于这个公式,有一件事可能令人惊讶,那就是随着我们增加扇出,我们必须关心的儿童百分位数增长得如此之快。即使对20个孩子进行扇出,并监控父母的$90^^$百分位数延迟,我们也需要关心孩子的$99.47^$百分位数延迟!
下图显示了随着扇出大小的增长,$50^{th}$、$90^{th}$和$99^{th}$父延迟聚合为由$100^{th}$百分位子延迟驱动的速度。
我们可以看到,如果我们监测父母的P90潜伏期,当我们分散到10个孩子的时候,我们将不得不开始关心孩子的P99潜伏期。
虽然这个公式很有意义,而且它背后的直觉听起来也是合理的4,但我经常发现,当我面对一个公式时,通过模拟来验证它的正确性是很有帮助的。因此,我下面是一个小小的模拟和可视化来做到这一点。当您将鼠标悬停在任一分布中的条形图上时,主要效果就会显现出来,因为可视化既会突出显示其分布中的百分位数,也会突出显示另一个分布中相应的百分位数:
上面是从一个通用工具生成的可视化示例,模拟3000个请求到一个父对象,分散到20个子对象,其中子延迟分布是对数正态分布,其中$\µ=3.4$,$\sigma=0.3$。工具本身驻留在这里,代码在这个GitHub资源库中。
现在我们可以使用这种可视化来经验性地确认我们的公式。如果我们将鼠标悬停在与父分布中的P95延迟相关的条上,我们的公式预测它对应的子延迟应该是$\sqrt[20]{0.95}\大约.9974$或p99.74,这大致就是我得到的结果。
虽然我们现在有了一种很好的方式来推理和可视化问题的最简单版本,但仍然有许多变体可以帮助我们理解。
以下是我们可能想要对系统执行的一些操作,以改善总体延迟:
如果我们重试最慢的子请求,如果它花费的时间“太长”,会发生什么?
如果我们将每个子请求发送给两个子请求,并且只使用最快的响应,会发生什么情况呢?
我不是第一个考虑这一点的人,这一部分的很多想法都是直接从尾巴上窃取的,还有我在野外观察到的东西。
这是一个相当丰富的主题,所以希望在未来我可以重新访问它,更新可视化以考虑到这些微妙之处。
感谢Jason帮我写出了这个公式,也感谢Paul Khuong和Ray Yang阅读了这篇文章的早期草稿。
有趣的是,我发现对数正态分布对于真正的延迟分布来说是正确的形状,大多数响应聚集在一个值周围,但有一个较慢的延迟尾部,所以我们将在本文的其余部分使用它来建模子延迟。我不知道该怎么跟你说,我不是真正的统计学家。你现在得到的是你在这里付出的代价。↩。
实际上,我不确定您是否可以合理地假设任何现实的分布式系统中的子延迟分布都是IID,但老实说,我不知道足够多的统计数据来做得更好。↩。
在调查我自己的延迟问题时,它似乎确实是轶事般的正确。↩