重新思考:Netflix 的边缘负载均衡

news2024/11/14 5:36:26
声明

本文是对Netflix 博客的翻译

前言

​ 在先前关于Zuul 2开源的文章中,我们简要概述了近期在负载均衡方面的一些工作。在这篇文章中,我们将更详细地介绍这项工作的原因、方法和结果。

​ 因此,我们开始从Zuul和其他团队那里学习,并改进我们的负载均衡实现,以进一步减少由服务器过载引起的错误。

背景

​ 在Zuul的实现中,我们持续采用Ribbon负载均衡器,它运用轮询算法和过滤机制,以识别并隔离连接失败率高的服务器。

​ 多年来,我们经历了一些改进和定制,旨在向最近启动的服务器发送更少的流量,以避免它们过载。这些方法已经取得了显著的改进,但是对于一些特别麻烦的源集群,我们仍然会看到与负载相关的错误率比期望的要高得多。

​ 如果集群中的所有服务器都过载,那么我们在选择一个服务器而不是另一个服务器时几乎没有什么改进,但是我们经常看到只有一部分服务器过载的情况。例如:

  • 启动后的冷服务器(在红黑部署和自动伸缩事件期间)
  • 由于交错的动态属性/脚本/数据更新或大型GC事件,服务器暂时变慢/阻塞。
  • 服务器硬件不好。我们经常会看到一些服务器的运行速度永远比其他服务器慢,这可能是由于邻居的噪音或不同的硬件。
指导原则

​ 项目启动之初,确立一些核心原则至关重要,它们将引导我们在软件开发过程中做出明智的决策。

在现有负载均衡器框架的约束下工作

​ 我们将之前的负载均衡定义与Zuul代码库相结合,这使得我们无法与Netflix的其他团队共享这些功能。所以这次我们决定接受约束和额外的投资,并从一开始就考虑重用。这使得在其他系统中的采用更加直接,并减少了重新发明轮子的机会。

运用从别人那里学到的东西

​ 尝试在别人的想法和实现的基础上进行构建。例如,之前在Netflix在其他IPC堆栈中试用的choice-of-2和试用算法。

避免分布式状态

​ 更倾向于局部决策,以避免弹性问题、复杂性和跨集群协调状态的滞后。

避免客户端配置和手动调优

​ 我们多年来使用Zuul的操作经验表明,将服务配置的一部分置于不属于同一团队的客户端服务中……会导致问题。

​ 一个问题是,这些客户端配置往往与服务器端不断变化的现实不同步,或者在不同团队拥有的服务之间引入变更管理的耦合

​ 例如,升级了用于Service X的EC2实例类型,从而减少了该集群所需的节点。因此,现在Service Y中的“每台主机的最大连接数”客户端配置应该增加,以反映新增加的容量。应该先进行客户端更改,还是先进行服务器端更改,还是同时进行两者的更改?更有可能的是,设置被完全遗忘,导致更多的问题。

​ 在可能的情况下,不要配置静态阈值,而是使用根据当前流量、性能和环境变化的自适应机制。

负载均衡方法

​ 一个核心理念是,尽管客户端视角是获取服务器延迟信息的最佳途径,但服务器本身则是提供服务器利用率信息的最佳来源。结合这两个数据源应该会给我们带来最有效的负载均衡。

​ 我们使用了相互补充的机制组合,其中大多数已经被其他人开发和使用过,尽管以前可能没有以这种方式组合。

  • 在服务器之间进行选择的二选一(choice-of-2 algorithm)算法
  • 主要根据负载均衡器对服务器利用率的视图进行均衡
  • 其次,根据服务器的利用率视图进行均衡
  • 基于缓慢释放流量和服务器生命周期的机制,以避免新启动的服务器过载 - 避免刚启动的服务突然接受流量洪峰顶不住
  • 收集到的服务器指标随时间衰减为零 - 服务指标衰减是为了避免指标差的服务永远得不到请求的机会
将最短队列加入与服务器报告的利用率相结合

​ 我们决定采用广泛使用的最短队列连接(JSQ)算法,并结合基于服务器自报利用率的算法,以期达到两者优势的融合。

​ 加入最短队列对于单个负载均衡器非常有效,但如果是在集群中的负载均衡,则会出现严重问题。问题是,负载均衡器将倾向于惊群(Herd),并同时选择相同的低利用率服务器,从而使它们过载,然后转移到下一个利用率最低的服务器并使其过载,然后继续……

​ 这可以通过结合使用JSQ和choice-of-2算法来解决。这在很大程度上消除了“**惊群”**问题,除了每个负载均衡器没有完整的服务器利用率之外,它工作得很好。

​ JSQ通常通过仅从本地负载均衡器计算到服务器的正在使用的连接数来实现,但是当有10到100个负载均衡器节点时,本地视图可能会产生误导。

在这里插入图片描述

​ 例如在上图中,负载均衡器A有一个流量请求服务X和Z,但是没有请求到Y。当有一个新的请求到来时他应该请求哪个服务,从数据上看应该选择Y。但这是不正确的,实际上其他两个负载均衡器正在请求他,他已经利用率最大了,但是负载均衡器A没法知道。

​ 这说明了单个负载均衡器的视角如何与实际情况完全不同

​ 我们在只依赖客户端视图时遇到的另一个问题是,对于大型集群(特别是在低流量的情况下),负载均衡器通常只有几个正在使用的连接,连接到数百个池中的服务器子集。因此,当它选择哪个服务器负载最少时,它通常只能在零和零之间进行选择。它没有任何关于它所选择的服务器的利用率的数据,因此只能随机猜测。

​ 这个问题的一个解决方案可能是与所有其他负载均衡器共享每个负载均衡器的进行中的请求数量,但这样你就有一个分布式状态问题需要解决。

​ 我们通常将分布式可变状态作为最后的手段,因为所获得的价值需要超过所涉及的实质性成本:

  • 操作开销和复杂性会增加,比如部署和金丝雀发布
  • 与数据损坏的爆炸半径相关的弹性风险(即 1% 的负载均衡器上的坏数据会令人烦恼,100% 的负载均衡器上的坏数据会导致中断
  • 在负载均衡器之间实现P2P分布式状态系统的成本,或者操作具有处理大量读写流量所需的性能和弹性凭证的单独数据库的成本。

​ 另一种更简单的解决方案——也是我们选择的一种——是依赖服务器向每个负载均衡器报告它们的使用情况……

利用率

​ 采用每台服务器对其利用率的自我评估的优势在于,它汇总了所有负载均衡器对该服务器的使用情况,从而解决了JSQ算法可能存在的信息不全面问题。

我们可以通过两种方式实现这一点:

  • 使用运行状况检查端点主动轮询每个服务器的当前利用率。
  • 被动地跟踪带有当前利用率数据注释的服务器的响应

​ 我们选择了第二个选项,因为它很简单,允许频繁更新这些数据,并且避免了让N个负载均衡器每隔几秒钟轮询M个服务器而给服务器带来的额外负载。

​ 这种被动策略的一个影响是,负载均衡器向一台服务器发送请求的频率越高,它对该服务器利用率的看法就越最新。所以RPS越高,负载均衡的效率就越高。但反过来说,RPS越低,负载均衡的效果就越差。

​ 这对我们来说不是问题,但对于通过一个特定负载均衡器接收低RPS的服务(同时通过另一个单独的负载均衡器接收高RPS),主动轮询运行状况检查可能更有效。临界点是负载均衡器向每个服务器发送的RPS低于用于运行状况检查的轮询频率。

服务器实现

​ 我们在服务器端实现了这一点,通过简单地跟踪进行中的计数,将其转换为该服务器配置的最大值的百分比,并将其作为HTTP响应头写出来:

X-Netflix.server.utilization: <current-utilization>[, target=<target-utilization>]

​ 可选的目标利用率可以由服务器指定,以指示它们在正常条件下打算使用的利用率百分比。然后,负载均衡器将使用它进行一些粗粒度的过滤,如后面所述。

​ 我们尝试使用进行中请求以外的指标,如操作系统报告的cpu利用率和平均负载,但发现它们似乎会引起振荡,这是由于它们基于滚动平均值而引起的延迟。因此,我们决定现在只使用计算飞行请求的相对简单的实现。

Choice-of-2 算法代替轮循

​ 由于我们希望能够通过比较服务器的统计信息来选择服务器,因此必须放弃现有的简单循环实现。

在Ribbon中,我们尝试了将JSQ与ServerListSubsetFilter结合的另一种方案,目的是减轻分布式JSQ可能导致的惊群问题。这给出了合理的结果,但是最终的跨目标服务器的请求分布仍然太广。

​ 因此,我们转而应用了Netflix另一个团队的一些早期经验,并实现了“二选一”算法。这样做的优点是易于实现,使负载均衡器上的cpu成本保持在较低水平,并提供良好的请求分发。

根据综合因素选择

​ 在选择服务器时,我们依据三个独立因素对它们进行评估:

  1. 客户端运行状况:该服务器的连接错误率的滚动百分比。
  2. 服务器利用率:该服务器提供的最新分数。
  3. 客户端利用率:从此负载均衡器发送到该服务器的当前请求数。

这3个因素被用来为每个服务器分配分数,然后比较总分数来选择获胜者。

使用像这样的多个因素确实会使实现变得更加复杂,但它可以避免仅依赖一个因素可能出现的边缘情况问题。

例如,如果一台服务器开始出现故障并拒绝所有请求,那么报告的利用率将会低得多——因为拒绝请求比接受请求快——如果仅有一个评判因子,那么所有负载均衡器将开始向该故障服务器发送更多请求。客户机综合选择因素缓解了这种情况。

过滤

​ 在随机挑选两台服务器进行比较的过程中,我们将排除那些利用率和运行状况超出我们设定的保守阈值的服务器。

​ 此过滤针对每个请求执行,以避免仅定期过滤的过时问题。为了避免在负载均衡器上造成高cpu负载,我们只尽最大努力进行N次尝试,以找到一个随机选择的可行服务器,然后在必要时退回到未过滤的服务器。

​ 当大部分服务器池存在持续的问题时,这样的过滤非常有用。在这种情况下,随机选择2个服务器经常会导致选择2个坏服务器进行比较,即使有许多好的服务器可用。

缓慢释放

​ 对于负载均衡器尚未收到响应的任何服务器,我们一次只允许一个进行中请求。我们过滤掉这些试用中的服务器,直到从它们那里收到响应。

​ 这有助于避免在新启动的服务器有机会表明它们的利用情况之前,因为大量的请求而使服务器过载。

基于服务器生命周期的预热

​ 我们依据服务器的生命周期,在新服务器启动后的最初90秒内,逐步增加其接收的流量。

统计衰变

​ 为避免服务器被永久性地列入黑名单,我们对所有用于负载均衡决策的统计数据实施了衰减机制(目前设定为超过30秒的线性衰减)。例如,如果服务器的错误率上升到80%,我们停止向它发送流量,那么我们使用的值将在30秒内衰减到零(即。15秒后会变成40%)。

操作的影响

​ 放弃使用轮询来实现负载均衡的一个负面影响是,以前我们在集群中的服务器之间有一个非常紧密的请求分布,现在我们在服务器之间得到了更大的增量。

​ 使用choice-of-2算法有助于减轻这种情况(与跨集群中所有服务器或服务器子集的JSQ相比),但不可能完全避免这种情况。

​ 因此,在操作方面确实需要考虑到这一点,特别是在金丝雀分析中,我们通常会比较请求计数、错误率、cpu等的绝对值。

较慢的服务器接收较少的流量

​ 显然,这是预期的效果,但对于使用 round-robin 的团队来说,流量是平均分配的,这对操作方面有一些连锁反应。

由于源服务器之间的流量分布现在取决于它们的利用率,如果一些服务器正在运行效率更高或更低的不同版本,那么它们将接收或多或少的流量。所以:

  • 当集群进行红黑部署时,如果新的服务器组有性能退化,那么该组的流量比例将小于50%
  • 同样的效果也可以在金丝雀上看到——基线可能接收到与金丝雀集群不同的流量。所以在查看参数时,最好将RPS和CPU结合在一起(游戏邦注:例如RPS在金丝雀中较低,而CPU则相同)
  • 不太有效的异常检测——我们通常有自动化来监视集群内的异常服务器(通常是由于某些硬件问题而从启动开始就变慢的VM)并终止它们。当这些异常值由于负载均衡而接收到较少的流量时,这种检测就更加困难
滚动动态数据更新

​ 从 round-robin 到这个新的负载均衡器的有用效果是,它可以很好地与动态数据和属性的分阶段推出一起工作。

​ 我们的最佳实践是一次部署一个区域(数据中心)的数据更新,以限制意外问题的影响范围。

​ 即使没有数据更新本身引起的任何问题,服务器应用更新的行为也可能导致短暂的负载峰值(通常与GC相关)。如果此峰值同时发生在集群中的所有服务器上,则可能导致负载减少和错误向上游传播的大峰值。在这种情况下,负载均衡器几乎无能为力,因为所有服务器都在承受高负载。

综合负载测试结果

​ 在开发、测试和调优这个负载均衡器的不同方面时,我们广泛地使用了综合负载测试场景。这些对于验证真实集群和网络的有效性非常有用,作为单元测试之上的可重复步骤,但尚未使用真实的用户流量。

关于这个测试的更多细节将在后面的附录中列出,但总结一下要点:

  • 与轮循实现相比,启用了所有特性的新负载均衡器在负载减少和连接错误方面有了数量级的减少。
  • 在平均和长尾延迟方面有了实质性的改进(与轮询实现相比减少了3倍)。
  • 服务器利用率特性本身的添加增加了显著的价值,提供了一个数量级的错误减少和大部分延迟减少。

对实际生产流量的影响

​ 我们发现新的负载均衡器在将尽可能多的流量分配到每个源服务器上时非常有效。这在不需要任何人工干预的情况下,可以绕过间歇性和持续降级的服务器进行路由,从而避免了导致工程师在半夜被唤醒的重大生产问题。

​ 在正常运行期间,很难说明这种影响,但在生产事件期间,甚至在某些服务的正常稳态运行期间,都可以看到这种影响。

进行中事件

​ 最近的一个事件涉及到服务中的一个bug,该bug导致越来越多的服务器线程随着时间的推移而阻塞。从服务器启动开始,每小时会有几个线程阻塞,直到它最终达到最大值并释放负载。

​ 在下面的每台服务器的RPS图表中,您可以看到,在凌晨3点之前,服务器之间存在广泛的分布。这是由于阻塞线程数量较多的服务器通过负载均衡器发送的流量较少。然后在凌晨3点25分之后,自动伸缩开始启动更多的服务器,每个服务器的RPS大约是现有服务器的两倍——因为它们还没有任何阻塞的线程,因此可以成功处理更多的流量。

​ 现在,如果我们看一下同一时间范围内每台服务器的错误率图表,您可以看到,在整个事件过程中,所有服务器的错误分布是相当均匀的,尽管我们知道有些服务器的容量比其他服务器小得多。这表明负载均衡器在有效地工作,并且由于集群中的总体可用容量太少,所有服务器的负载都略微超过了它们的有效容量。

​ 然后,当自动扩展启动新服务器时,它们会被发送尽可能多的流量,直到它们与集群的其他服务器在相同的低水平上出错。

总之,负载均衡在将流量分配给服务器方面非常有效,但在本例中,没有足够多的新服务器启动,因此无法将总体错误级别降至零。

稳态

​ 我们还看到,在一些服务器的服务中,由于GC事件而出现几秒钟的负载下降,仅在稳态噪声方面就有了显著的减少。在启用新的负载均衡器后,可以看到错误大大减少:

警告间隙

​ 一个意想不到的影响是突出了我们自动警报中的一些漏洞。一些现有的基于服务错误率的警报,以前会在渐进问题只影响集群的一小部分时触发,现在会延迟触发,或者根本不会触发,因为错误率保持在较低水平。这意味着团队有时没有收到影响其集群的大问题的通知。解决方案是通过在利用率指标中添加额外的偏差警报(而不仅仅是错误指标)来填补这些差距。

结论

​ 本文的主旨不在于推广Zuul——尽管它无疑是一个卓越的系统——而是在于与代理、服务网格、负载均衡社区分享经验,为其中那些引人入胜的方法增添新的视角。Zuul是测试、实现和改进这些类型的负载均衡方案的好系统;根据Netflix的需求和规模来运行它们,使我们有能力证明和改进这些方法。

与任何软件系统一样,您应该根据自己组织的约束条件和目标做出决策,并尽量避免追求完美。

如果你对这类工作感兴趣,请随时联系我或我们Netflix的云网关团队。

Reference
  1. 原文链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1696141.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【限免】杂波环境下线性调频脉冲、巴克码、频率步进脉冲雷达MTI、脉冲压缩【附MATLAB代码】

来源&#xff1a;微信公众号&#xff1a;EW Frontier 本代码主要模拟杂波环境&#xff08;飞机、地杂波、鸟类信号&#xff09;下&#xff0c;Chirp脉冲、巴克码脉冲、频率步进脉冲雷达信号的脉冲压缩及MTI、​匹配滤波。 MATLAB主代码 % 定义参数 fs 1000; % 采样率 T 1; …

C语言 | Leetcode C语言题解之第106题从中序与后序遍历序列构造二叉树

题目&#xff1a; 题解&#xff1a; int post_idx;typedef struct {int key;int val;UT_hash_handle hh; } hashTable;hashTable* idx_map;void insertHashTable(int x, int y) {hashTable* rec malloc(sizeof(hashTable));rec->key x;rec->val y;HASH_ADD_INT(idx_m…

九、图形化脚本

多年来&#xff0c; shell脚本一直都被认为是枯燥乏味的。但如果你准备在图形化环境中运行脚本时&#xff0c;就未必如此了。有很多与脚本用户交互的方式并不依赖read和echo语句。 9.1 创建文本菜单 创建交互式shell脚本最常用的方法是使用菜单。提供各种选项可以帮助脚本用户…

牛客NC295 连续子链表最大和【simple 动态规划 Java/Go/PHP/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/650b68dfa69d492d92645aecd7da9b21 思路 动态规划动态规划算法通过迭代遍历输入数组&#xff0c;维护一个额外的数组 dp 来记录截止到每个位置的最大连续子数组和&#xff0c;并利用一个变量 max_num 实时更新全…

面向可复用性和可维护性的设计模式 课程学习总结

什么是设计模式 设计模式&#xff1a;在软件设计中给定上下文中常见问题的通用的、可重用的解决方案。 设计模式分类 1. 创建型模式——Creational patterns 关注对象创建的过程 1.1 工厂方法模式 定义用于创建对象的接口&#xff0c;但让子类决定要实例化哪个类。工厂方…

swift中json和字典Dict或者数组相互转换,JSONSerialization的强大使用

在Swift中&#xff0c;你可以使用JSONSerialization类将JSON字符串转换为字典。要将 Swift 字典转换为 JSON 字符串&#xff0c;我们可以使用JSONSerialization类的data(withJSONObject:options:)方法。这个方法将字典转换为二进制数据&#xff0c;然后我们可以使用String(data…

Leetcode 环形链表|| 快慢指针解法

但是我们不知道 aaa 的值&#xff0c;该怎么办&#xff1f;依然是使用双指针法。考虑构建一个指针&#xff0c;此指针需要有以下性质&#xff1a;此指针和 slow 一起向前走 a 步后&#xff0c;两者在入口节点重合。那么从哪里走到入口节点需要 aaa 步&#xff1f;答案是链表头节…

SAP-CO成本控制概念之标准成本

“ 本篇介绍&#xff1a;标准成本的会计概念&#xff0c;标准成本的制定标准&#xff1b;通过结合会计标准成本的概念与SAP CO标准成本估算功能&#xff0c;更具象化的了解SAP如何实现标准成本管理&#xff0c;为后续学习SAP实际成本核算打下基础。” 01 — 背景需求 SAP实施…

【C++】深入解析C++智能指针:从auto_ptr到unique_ptr与shared_ptr

文章目录 前言&#xff1a;1. 智能指针的使用及原理2. C 98 标准库中的 auto_ptr:3. C 11 中的智能指针循环引用&#xff1a;shared_ptr 定制删除器 4. 内存泄漏总结&#xff1a; 前言&#xff1a; 随着C语言的发展&#xff0c;智能指针作为现代C编程中管理动态分配内存的一种…

infoq读书笔记-云原生时代,如何建设稳定性可观测体系?

而可观测性则是把Log、Trace、Metric拧成了一股绳&#xff0c;让三大支柱互相之间建立亲密的“血缘关系”&#xff0c;通过这种关系我们可以结构化的从整体到局部再到具体细节的观测业务&#xff1a; 图片来自网络如果把业务系统比作一座海上的冰山&#xff0c;监控仅能看到的…

02_前端三大件HTML

文章目录 HTML用于网页结构搭建1. 标签2. 客户端服务器交互流程3. 专业词汇4. html语法细节5. 安装VSCODE安装插件6. Live Server插件使用7. 标题&段落&换行&列表8. 超链接标签使用9. 图片10. 表格的写法11. 表单标签*(重点)12. 下拉框13. 页面布局标签14. 块元素和…

机器学习大模型驱动:未来的趋势与应用

文章目录 &#x1f4d1;前言一、什么是机器学习大模型&#xff1f;1.1 大模型的特点1.2 大模型的技术基础 二、大模型的技术实现2.1 Transformer 架构2.2 预训练和微调2.3 模型并行和数据并行 三、大模型的应用场景3.1 自然语言处理&#xff08;NLP&#xff09;3.2 计算机视觉&…

02324 自学考试 离散数学屈婉玲教材 目录

02324 自学考试 离散数学屈婉玲教材 目录 02324 自学考试 离散数学屈婉玲教材 02324离散数学全程班历年真题资料

21.2zabbix低级自动发现-mysql多实例

配置mysql多实例 注释&#xff1a;自动发现&#xff1a;创建监控主机&#xff1b;低级自动发现&#xff1a;创建监控项 mysql单实例是直接yum安装&#xff0c;开启mysql多实例 准备配置文件 #mysql3307实例 cp /etc/my.cnf /etc/my3307.cnf vim /etc/my3307.cnf [mysqld] dat…

Maven多环境打包配置

一、启动时指定环境配置文件 在启动springboot应用的jar包时&#xff0c;我们可以指定配置文件&#xff0c;通常把配置文件上传到linux服务器对应jar包的同级目录&#xff0c;或者统一的配置文件存放目录 java -jar your-app.jar --spring.config.location/opt/softs/applicat…

4.Redis之Redis的通用命令

0.Redis 实战操作 通过 redis-cli 客户端和 redis 服务器交互 涉及到很多的 redis 的命令 【redis 的命令非常非常多!!! 1.掌握常用命令(多操作多练习) 2.学会使用 redis 的文档-> 阅读文档, 是程序猿的基操!! redis 的命令非常非常多!!! 1.掌握常用命令(多操作多练习…

Golang文件操作

文章目录 文件操作基本介绍普通的文件操作方式&#xff08;os包&#xff09;带缓冲的文件操作方式&#xff08;bufio包&#xff09;文件拷贝操作&#xff08;io包&#xff09; 命令行参数基本介绍解析命令行参数&#xff08;flag包&#xff09; JSON基本介绍JSON序列化JSON反序…

【手把手带你搓组件库】从零开始实现Element Plus

从零开始实现Element Plus 前言亮点项目搭建1、创建项目初始化monorepo创建 .gitignore目录结构安装基础依赖配置文件创建各个分包入口utilscomponentscoreplaytheme 2、创建VitePress文档3、部署到Github Actions生成 GH_TOKENGitHub Page 演示 4、总结 前言 在本文中&#xf…

vim操作手册

vim分为插入模式、命令模式、底行模式。 插入模式&#xff1a;编辑模式 命令模式&#xff1a;允许使用者通过命令&#xff0c;来进行文本的编辑控制 底行模式&#xff1a;用来进行让vim进行包括但不限于shell进行交互 w&#xff1a;保存 wq&am…

北邮22级信通院DSP:用C++程序实现给定参数下四种滤波器的Butterworth模拟滤波器设计:给定上下截频和衰减系数求H(p)和H(s)

北邮22信通一枚~ 跟随课程进度更新北邮信通院DSP的笔记、代码和文章&#xff0c;欢迎关注~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院DSP_青山入墨雨如画的博客-CSDN博客 目录 一、 核心算法 1.1判断滤波器类型 1.2 带通滤波器BP 1.3带阻滤波器B…