最近通过Nginx来反向代理一批大模型服务,遇到一个典型问题。默认的轮训负载均衡场景下,如果用户的每次请求到达算法服务时,由于不同的问题导致算法返回的Token长度不一致。就会出现某些算法Pod在上轮问答还没结束时收到了下次的请求。由于Nginx或负载均衡器上无法预测上游算法的Token长度,只能暴力的讲请求轮训分发到后端,长此以往,就导致后端算法服务随机出现阻塞的问题。
我原本是想利用ngx_http_dyups_module
模块来开放一个URI来让后端算法根据当前的任务动态调整自己在Nginx Upstream上的权重,类似这样
GET http://nginx-serger:7080/dynamic?upstream=zone_for_backends&server=127.0.0.1:8080&weight=10&max_fails=5&fail_timeout=5
img_v2_07d6c717-021b-401c-a9de-9591fe2c8eeg
但这个方案无法在Kubernetes Ingress Nginx上实现,且在业务层有代码侵入到代码,虽然工作量不大,但感觉实践起来比较别扭。
转机,再一次意外的网上冲浪过程中,我发现Ingress Nginx上支持EWMA
调度算法,虽然以前看过,但仅限于了解用来根据响应耗时来做负载均衡。仔细想了想,上面的问题本质上也是个长链接场景下的负载均衡问题
。便在Ingress Nginx上开启了试下,果然Work! 之前多副本情况下算法服务的Pod CPU总是出现旱的旱死,涝的涝死的情况,开启之后整体来看,较为均衡。
image-20230608131148502
以下对我了解EWMA帮助比较大的文章,大家值得阅读
原文出处 https://linkerd.io/2016/03/16/beyond-round-robin-load-balancing-for-latency/
负载均衡是任何大规模软件部署的关键组成部分。但进行负载均衡的方法有很多,哪种方式最好呢?我们如何评估不同的选项?
在现代软件生态系统中,负载均衡起到了几个角色。首先,它是可扩展性的基础。软件被部署为多个相同的副本。我们通过部署额外的副本来“扩展”软件。流量必须在各副本间分配,而这个分配就是负载均衡。
负载均衡提供了另一个必要的特性:即弹性。直观地讲,一个具有弹性的系统是指,即使单个组件失败,也不会导致整个系统失败的系统。软件或者运行软件的硬件都会失败。通过只将流量分配给能够提供服务的实例,负载均衡使我们能够将多个可能出错的组件连接成一个具有弹性的系统。
我们可以将这种弹性模型进一步扩展,以应对分布式系统中的另一个不受欢迎的访问者:延迟。就像系统的组件可能会失败一样,它们也可能变慢。一个好的负载均衡器必须能够防止延迟,就像它防止失败一样。即使在存在慢副本的情况下,整个系统也必须保持快速。
这第三个标准比前两个微妙。从算法上讲,处理可扩展性和弹性是直接的:给定一组副本,将流量分配到所有活跃的副本,并不把流量分配给已经失败的副本。(我们暂时忽略评估副本健康状况的挑战。)对于延迟,情况就不那么明确了:给定一组以各种速度运行的副本,将负载分配给它们的最佳策略是什么?
在这篇文章中,我们使用三种算法进行了一个简单的实验:轮询、最少负载以及峰值指数加权移动平均(“Peak EWMA”)。这三种算法作为一个测试床,展示了正确或错误的负载均衡算法选择可能带来的影响。特别是,我们测试了这些算法处理组件延迟的有效性。
简单来说,这三种算法的行为如下:
在这三种算法中,轮询在实践中常见,大多数软件负载均衡器,包括Nginx和HAProxy,都支持。另外两种算法在实践中较少见,但在Twitter的客户端RPC库Finagle中,都有经过生产测试的实现。
请注意,对负载均衡策略的选择应根据您的具体业务需求和系统特性进行。不同的系统可能更适合使用不同的负载均衡策略。最好的方式是进行实验,看看在你的特定环境和工作负载下哪种策略效果最好。
我们进行了一个简单的模拟实验,使用以下场景测量组件延迟对整个系统延迟的影响:
我们使用基于Finagle编写的基础RPC客户端进行这些实验。
实验结果如上图所示。y轴表示延迟,x轴(对数刻度)表示超过该延迟的延迟分布中的百分位数。
在面对慢服务器的情况下,三种算法的性能差异非常明显。轮询受影响最大,显示出在95百分位以上的慢速性能。最少负载表现得更好,保持快速性能直到99百分位,而峰值EWMA表现得更好,保持速度直到99.9百分位。
由于在分布式系统中,延迟和失败经常通过超时绑定在一起,我们也可以用失败来表达结果。如果我们系统的调用者使用1秒的超时,那么使用轮询的成功率大约是95%,使用最少负载的成功率是99%,而使用峰值EWMA的成功率是99.9%——这是一个显著的差异。
从三种算法中可以看出,轮询明显是表现最差的。在某些方面,这并不令人惊讶:因为它也是最简单的算法。
然而,轮询不仅仅是一个更糟糕的算法——它没有利用最少负载和峰值EWMA可以利用的信息。因为Finagle在OSI模型的第5层(“会话”层)上操作,它可以获取到队列深度和RPC延迟等信息。最少负载利用了队列深度,并显示出明显优于轮询的性能;峰值EWMA考虑了RPC延迟和队列深度,表现出更好的性能。这三个选项之间的差异并不仅仅是算法上的,而更多的是用来做负载均衡决策的信息的差异。
当然,在实践中,影响负载均衡性能的因素远不止算法的选择。在高并发下,实现可能性能较差。算法可能不适合某些类别的请求,如长轮询请求(在这种情况下,高延迟是预期的,而不是失败的症状)。算法可能不适合特定的客户端/服务器关系,例如高度不对称的副本数量。在这篇文章中,我们并没有试图提供全面的分析,并且只花费了最少的努力来控制这些变量。我们的意图仅仅是提供一个算法选择可能产生的差异的例子。
也就是说,Twitter的大规模生产实验已经验证了最少负载和峰值EWMA(以及其他负载均衡算法)的有效性,并且这些算法被大规模地用于支持Twitter今天的大部分基础设施。
对于负载均衡高级连接(如RPC或HTTP调用)的系统,其中第5层信息(如端点延迟和请求深度)可用,当有慢速端点存在时,轮询负载均衡可能比其他算法表现得要差得多。通过使用可以利用第5层信息的算法,这些系统可能在面对慢速端点时显示出显著改善的性能。
如果上述结果适用于你的情况,你可能需要利用像最少负载和峰值EWMA这样的算法。