翻译自外网的一篇博客,作者以可视化动画的方式,对负载均衡等概念进行了解释。建议大家阅读原文,定会受益匪浅
负载均衡
超过某一点后,web应用程序的规模将超过单个服务器的部署。公司要么希望提高可用性、可扩展性,要么两者兼而有之!为了做到这一点,他们在多个服务器上部署应用程序,并在前面部署一个负载均衡器来分发传入的请求。大公司可能需要数千台运行其web应用程序的服务器来处理负载。
在这篇文章中,我们将重点讨论单个负载均衡器将HTTP请求分发到一组服务器的方式。我们将从底层开始,逐步发展到现代负载平衡算法。
将问题可视化
让我们从一开始:一个负载均衡器向一个服务器发送请求。以每秒1个请求(RPS)的速率发送请求,并且随着服务器的处理,每个请求的大小都会减小。
对于许多网站来说,这种设置效果很好。现代服务器功能强大,可以处理大量请求。但是当他们跟不上的时候会发生什么呢?
在这里,我们可以看到,3个RPS的速率会导致一些请求被丢弃 。如果请求到达服务器 ,而另一个请求正在处理中, 服务器将丢弃它。这将导致向用户显示错误,这是我们希望避免的。我们可以将另一个服务器添加到我们的负载均衡器中以解决此问题。
不再有丢弃请求!我们的负载均衡器在这里的行为方式,依次向每个服务器发送请求,称为“循环”负载平衡。这是最简单的负载平衡形式之一,当您的服务器都同样强大,而您的请求都同样昂贵时,它可以很好地工作。
什么时候round robin失效
在现实世界中,很少有服务器同样强大,请求同样昂贵。即使使用完全相同的服务器硬件,性能也可能有所不同。应用程序可能必须为许多不同类型的请求提供服务,并且这些请求可能具有不同的性能特征。
让我们看看当我们改变请求成本时会发生什么。在下面的模拟中,请求并不同样花费。你可以通过一些请求来看到这一点,这些请求需要比其他请求更长的时间来收缩。
虽然大多数请求都得到了成功的服务,但我们确实放弃了一些请求。我们可以缓解这种情况的方法之一是建立一个“请求队列”
请求队列有助于我们处理不确定性,但这是一种权衡。我们将丢弃更少的请求,但代价是一些请求具有更高的延迟。如果你看上面的模拟足够长的时间,你可能会注意到请求微妙地改变了颜色。它们等待处理的时间越长,颜色就会改变得越多。您还会注意到,由于请求成本差异,服务器开始出现不平衡。当服务器运气不好时,必须连续提供多个昂贵的请求,队列就会被阻塞。如果队列已满,我们将丢弃请求。
上述内容同样适用于功率不同的服务器。在接下来的模拟中,我们还改变了每个服务器的处理能力,这在视觉上用较深的灰色阴影表示。
服务器被赋予了一个随机的性能,但有可能有些服务器的性能不如其他服务器,并迅速开始丢弃请求。与此同时,处理能力更强大的服务器大部分时间处于空闲状态。这种情况显示了循环赛的关键弱点:方差。
然而,尽管存在缺陷,round-robin仍然是nginx默认的HTTP负载平衡方法。
对round robin升级
可以调整round robin算法,以便在有差异的情况下表现得更好。有一种称为“加权循环”的算法,它包括让人类用一个权重标记每个服务器,该权重决定向其发送多少请求。
在这个模拟中,我们使用每个服务器的已知性能作为其权重,当我们循环遍历它们时,我们会给更强大的服务器更多的请求。
虽然这比普通循环法更好地处理了服务器功率的差异,但我们仍有请求差异需要处理。在实践中,让人类手动设定权重很快就会失败。将服务器的性能线性描述成数字是很困难的,并且需要对实际工作负载进行仔细的负载测试。很少有人会去这样做,因此加权循环的另一种变体通过使用代理度量动态计算权重:延迟。
理所当然的是,如果一个服务器提供请求的速度是另一个服务器的3倍,那么它可能会快3倍,并且应该比另一个服务多接收3倍的请求。
这次我向每个服务器添加了文本,显示了最后3个请求的平均延迟。然后,我们根据延迟的相对差异,决定是向每个服务器发送1个、2个还是3个请求。结果与初始加权循环模拟非常相似,但无需预先指定每个服务器的权重。该算法还能够适应服务器性能随时间的变化。这被称为“动态加权循环”
其它的方法
动态加权循环似乎很好地解释了服务器功率和请求成本的差异。但如果我告诉你我们可以做得更好,用一个更简单的算法呢?
这被称为“最少连接”负载平衡。
因为负载均衡器位于服务器和用户之间,所以它可以准确地跟踪每个服务器有多少未处理的请求。然后,当一个新的请求到来,是时候确定将其发送到哪里了,它就会知道哪些服务器需要做的工作最少,并优先考虑这些服务器。
无论存在多大的方差,该算法都表现得非常好。它通过保持对每个服务器正在做什么的准确理解来消除不确定性。它还具有实现起来非常简单的优点。由于这些原因,您会发现该算法是AWS负载均衡器的默认HTTP负载平衡方法。它也是nginx中的一个选项,如果你从未更改过默认值,那么它非常值得尝试。
虽然该算法在简单性和性能之间取得了很好的平衡,但它也不能避免丢弃请求。然而,您会注意到,该算法丢弃请求的唯一时间是实际上没有更多可用的队列空间。它将确保所有可用资源都在使用中,这使它成为大多数工作负载的一个很好的默认选择。
对延迟的优化
到目前为止,我一直在回避讨论的一个关键部分:我们正在优化什么。言外之意,我一直认为丢弃请求真的很糟糕,并试图避免它们。这是一个不错的目标,但它不是我们最想在HTTP负载均衡器中优化的指标。
我们通常更关心的是延迟。这是以毫秒为单位测量的,从创建请求的那一刻到它得到服务的那一时刻。当我们在这种情况下讨论延迟时,通常会谈论不同的“百分位数”。例如,第50百分位数(也称为“中位数”)被定义为毫秒值,其中50%的请求低于该值,50%的请求高于该值。
我在60秒内用相同的参数进行了3次模拟,并每秒进行各种测量。每个模拟仅因所使用的负载平衡算法而异。让我们比较3种模拟中每种模拟的中值:
(横轴是时间,纵轴是相应的延迟)
您可能没有预料到,但循环具有最佳的中位延迟。如果我们不看任何其他数据点,我们就会错过完整的故事。让我们看看第95个百分位数和第99个百分位位数。
注意:每个负载平衡算法的不同百分位数之间没有颜色差异。较高的百分位数在图表上总是较高的。
我们看到round robin在较高的百分位数中表现不佳。round robin怎么可能有一个很好的中位数,但糟糕的第95和第99个百分位数?
在round robin中,不考虑每个服务器的状态,因此会有相当多的请求流向空闲的服务器。这就是我们获得低50百分位的方法。另一方面,我们也会过多地向过载的服务器发送请求,因此出现了糟糕的第95和第99个百分位数。
我们可以查看直方图形式的完整数据:
我为这些模拟选择了参数,以避免丢弃任何请求。这保证了我们对所有3种算法进行相同数量的数据点比较。让我们再次运行模拟,但增加RPS值,旨在推动所有算法超过它们所能处理的范围。以下是一段时间内累积的请求丢弃的图表。
最少的连接可以更好地处理过载,但这样做的成本略高于95%和99%的延迟。根据您的用例,这可能是一个有价值的权衡。
新的算法PEWMA
如果我们真的想优化延迟,我们需要一种将延迟考虑在内的算法。如果我们能将动态加权循环算法与最小连接算法相结合,那不是很好吗?加权循环的延迟和最小连接的弹性。
事实证明,我们不是第一个有这种想法的人。以下是使用一种名为“峰值指数加权移动平均”(或PEWMA)的算法进行的模拟。这是一个漫长而复杂的名字,但请坚持住,我稍后会详细介绍它的工作原理。
我已经为这个模拟设置了特定的参数,这些参数可以保证表现出预期的行为。如果你仔细观察,你会注意到算法会在一段时间后停止向最左边的服务器发送请求。它这样做是因为它发现所有其他服务器都更快,并且不需要向最慢的服务器发送请求。这只会导致请求具有更高的延迟。
那么它是如何做到这一点的呢?它将动态加权循环赛的技术与最少连接的技术相结合,并在顶部撒上了一点自己的魔法。
对于每个服务器,算法跟踪从最后N个请求开始的延迟。它不是用它来计算平均值,而是用指数递减的比例因子对值进行求和。这会产生一个值,其中延迟越大,它对总和的贡献就越小。最近的请求比以前的请求对计算的影响更大。
然后取该值,再乘以到服务器的打开连接数,结果就是我们用来选择将下一个请求发送到哪个服务器的值。越低越好。
那么,两者相比如何呢?首先,让我们来看看第50个、第95个和第99个百分位数与之前的最少连接数据进行比较。
我们看到了全面的显著进步!它在较高的百分位数上更为明显,但在中位数上也一直存在。在这里,我们可以看到直方图形式的相同数据。
丢弃的请求怎么样?
它一开始性能更好,但随着时间的推移,时间性能差的服务器的连接少。这是有道理的。PEWMA是机会主义的,因为它试图获得最佳的延迟,这意味着它有时可能会让服务器负载不足。
我想在这里补充一点,PEWMA有很多参数可以调整。我为这篇文章编写的实现使用的配置似乎适用于我测试过的情况,但进一步的调整可以获得更好的结果,而不是最少的连接。这是PEWMA相对于最少连接的缺点之一:额外的复杂性。
结论
我在这篇文章上花了很长时间。很难在现实主义和通俗易懂之间取得平衡,但我对自己的着陆点感觉很好。我希望能够看到这些复杂系统在实践中,在理想和不太理想的场景中的表现,有助于您直观地了解它们何时最适合您的工作负载。