处理过载(google-sre-book译文)

处理过载

撰稿:Alejandro Forero Cuervo
编辑:Sarah Chavis

避免过载是负载平衡策略的目标。但无论您的负载平衡策略多么有效,最终系统的某些部分都会过载。妥善处理过载情况是运行可靠的服务系统的基础。

处理过载的一种选择是提供降级响应:响应不如正常响应准确或包含的数据少于正常响应,但更容易计算。例如:

  • 无需搜索整个语料库来为搜索查询提供最佳可用结果,而是仅搜索候选集的一小部分。
  • 依赖结果的本地副本,该副本可能不是完全最新的,但使用起来比违反规范存储更便宜。

然而,在极端过载的情况下,服务甚至可能无法计算和提供降级响应。此时,它可能没有立即选择,只能处理错误。缓解这种情况的一种方法是平衡数据中心之间的流量,这样任何数据中心接收的流量都不会超过其处理能力。例如,如果数据中心运行 100 个后端任务,并且每个任务每秒最多可以处理 500 个请求,则负载平衡算法将不允许每秒向该数据中心发送超过 50,000 个查询。然而,当您大规模运营时,即使这种限制也不足以避免过载。归根结底,最好构建客户端和后端以优雅地处理资源限制:尽可能重定向,必要时提供降级结果,

“每秒查询数”的陷阱

不同的查询可能具有截然不同的资源要求。查询的成本可能会根据任意因素而变化,例如发出查询的客户端中的代码(对于具有许多不同客户端的服务),甚至一天中的时间(例如,家庭用户与工作用户;或交互式最终用户流量)与批量流量相比)。

我们通过惨痛的教训学到了这一教训:将容量建模为“每秒查询数”或使用请求的静态特征,这些特征被认为是它们消耗的资源的代理(例如,“请求读取了多少个键”)通常会导致对于一个糟糕的指标。即使这些指标在某个时间点表现良好,比率也可能会发生变化。有时变化是渐进的,但有时变化是剧烈的(例如,软件的新版本突然使得某些请求的某些功能需要的资源显着减少)。移动目标对于设计和实施负载平衡来说是一个糟糕的指标。

更好的解决方案是直接测量可用资源的容量。例如,您可能为给定数据中心中的给定服务总共保留了 500 个 CPU 核心和 1 TB 内存。当然,直接使用这些数字来模拟数据中心的容量会效果更好。我们经常谈论请求的成本,是指它消耗了多少CPU时间的标准化度量(在不同的CPU架构上,考虑到性能差异)。

在大多数情况下(当然不是全部),我们发现简单地使用 CPU 消耗作为配置信号效果很好,原因如下:

  • 在具有垃圾收集的平台中,内存压力自然会转化为 CPU 消耗的增加。
  • 在其他平台中,可以以一种不太可能在 CPU 耗尽之前耗尽剩余资源的方式来配置剩余资源。

如果过度配置非 CPU 资源的成本过高,我们在考虑资源消耗时会单独考虑每个系统资源。

每个客户的限额

处理过载的一个组成部分是决定在全局过载的情况下该怎么做。在一个完美的世界中,团队与后端依赖项的所有者仔细协调其启动,全局过载永远不会发生,并且后端服务始终有足够的能力来服务客户。不幸的是,我们并不生活在一个完美的世界中。实际上,全局过载非常频繁地发生(特别是对于往往有许多团队运行许多客户端的内部服务)。

当全球过载确实发生时,至关重要的是该服务仅向行为不当的客户提供错误响应,而其他客户不受影响。为了实现这一结果,服务所有者根据与客户协商的使用情况来配置容量,并根据这些协议定义每个客户的配额。

例如,如果后端服务在全球范围内(在各个数据中心)分配了 10,000 个 CPU,则其每个客户的限制可能如下所示:

  • Gmail 每秒最多可以消耗 4,000 个 CPU 秒。
  • 允许日历每秒消耗最多 4,000 CPU 秒。
  • Android 允许每秒消耗最多 3,000 CPU 秒。
  • Google+ 每秒最多可以消耗 2,000 个 CPU 秒。
  • 其他每个用户每秒最多可以消耗 500 CPU 秒。

请注意,这些数字加起来可能超过分配给后端服务的 10,000 个 CPU。服务所有者所依赖的事实是,所有客户不太可能同时达到资源限制。

我们实时聚合来自所有后端任务的全局使用信息,并使用该数据对各个后端任务施加有效限制。仔细研究实现此逻辑的系统超出了本次讨论的范围,但我们已经编写了重要的代码来在后端任务中实现此逻辑。这个难题的一个有趣的部分是实时计算每个单独请求消耗的资源量(特别是 CPU)。对于没有实现每个请求一个线程模型的服务器来说,这种计算尤其棘手,在该模型中,线程池使用非阻塞 API 只执行所有请求的不同部分。

客户端节流

当客户超出配额时,后端任务应快速拒绝请求,并期望返回“客户超出配额”错误所消耗的资源比实际处理请求并返回正确响应要少得多。然而,这个逻辑并不适用于所有服务。例如,拒绝需要简单 RAM 查找的请求(其中请求/响应协议处理的开销明显大于生成响应的开销)与接受并运行该请求几乎同样昂贵。即使拒绝请求可以节省大量资源,这些请求仍然消耗一些资源。如果被拒绝的请求数量很大,这些数字就会迅速增加。在这种情况下,后端可能会过载,即使其绝大多数 CPU 都用于拒绝请求!

客户端限制解决了这个问题。106当客户端检测到其最近请求的很大一部分由于“超出配额”错误而被拒绝时,它会开始自我调节并限制其生成的传出流量。超过上限的请求在本地失败,甚至无法到达网络。

我们通过称为自适应节流的技术实现了客户端节流。具体来说,每个客户端任务都会保留其历史记录的最后两分钟的以下信息:

requests

应用层尝试的请求数(在客户端,在自适应节流系统之上)

accepts

后端接受的请求数

正常情况下,两个值相等。当后端开始拒绝流量时, 的数量accepts变得小于 的数量requests。客户端可以继续向后端发出请求,直到达到requests的K倍accepts。一旦达到该截止值,客户端就开始自我调节,并且新请求将在本地(即客户端)被拒绝,其概率在客户端请求拒绝概率中计算。

客户端请求拒绝概率

随着客户端本身开始拒绝请求,requests将继续超过accepts。虽然这看起来可能违反直觉,但考虑到本地拒绝的请求实际上并未传播到后端,这是首选行为。随着应用程序尝试向客户端请求的速率增加(相对于后端接受请求的速率),我们希望增加丢弃新请求的概率。

对于处理请求的成本非常接近拒绝该请求的成本的服务,允许大约一半的后端资源被拒绝的请求消耗是不可接受的。这种情况下,解决办法很简单:修改客户端请求拒绝概率( Client request Rejection Probability )中的接受乘数K(例如2)。这样:

  • 减少乘数将使自适应节流行为更加积极
  • 增加乘数将使自适应节流行为不那么激进

例如,不要让客户端在 时进行自我调节requests = 2 * accepts,而是让客户端在 时进行自我调节requests = 1.1 * accepts。将修饰符减少到 1.1 意味着每接受 10 个请求,后端只会拒绝 1 个请求。

我们通常更喜欢 2 倍乘数。通过允许比实际允许的更多请求到达后端,我们在后端浪费了更多资源,但我们也加快了状态从后端到客户端的传播。例如,如果后端决定停止拒绝来自客户端任务的流量,则所有客户端任务检测到此状态更改之前的延迟会更短。

我们发现自适应限制在实践中效果很好,可以实现总体请求率的稳定。即使在严重过载的情况下,后端最终也会拒绝其实际处理的每个请求。这种方法的一大优点是,决策是由客户端任务完全基于本地信息并使用相对简单的实现来做出的:没有额外的依赖性或延迟损失。

另一项考虑因素是,客户端限制可能不适用于仅偶尔向后端发送请求的客户端。在这种情况下,每个客户端对后端状态的了解大大减少,并且增加这种可见性的方法往往很昂贵。

关键性

临界性是我们发现在全球配额和限制背景下非常有用的另一个概念。向后端发出的请求与四个可能的关键值之一相关联,具体取决于我们认为该请求的关键程度:

CRITICAL_PLUS

为最关键的请求保留,这些请求如果失败将导致严重的用户可见影响。

CRITICAL

从生产作业发送的请求的默认值。这些请求会造成用户可见的影响,但影响可能没有CRITICAL_PLUS. 预计服务将为所有预期CRITICALCRITICAL_PLUS流量提供足够的容量。

SHEDDABLE_PLUS

预计部分不可用的流量。这是批处理作业的默认设置,可以在几分钟甚至几小时后重试请求。

SHEDDABLE

预期频繁部分不可用和偶尔完全不可用的流量。

我们发现四个值足够稳健,可以对几乎所有服务进行建模。我们就添加更多值的提案进行了各种讨论,因为这样做可以让我们更精细地对请求进行分类。然而,定义附加值将需要更多资源来操作各种关键性感知系统。

我们已将临界性作为 RPC 系统的首要概念,并努力将其集成到我们的许多控制机制中,以便在对过载情况做出反应时可以将其考虑在内。例如:

  • 当客户用完全局配额时,如果后端任务已经拒绝了所有较低关键性的所有请求,则后端任务只会拒绝给定关键性的请求(事实上,我们的系统支持的每个客户限制,如前所述,可以设置每个关键性)。
  • 当任务本身超载时,它会更快地拒绝重要性较低的请求。
  • 自适应节流系统还为每个关键性保留单独的统计数据。

请求的关键性与其延迟要求正交,从而与所使用的底层网络服务质量 (QoS) 正交。例如,当系统在用户键入搜索查询时显示搜索结果或建议时,底层请求是高度可摆脱的(如果系统过载,不显示这些结果是可以接受的),但往往具有严格的延迟要求。

我们还显着扩展了 RPC 系统以自动传播关键性。如果后端接收到请求A,并且作为执行该请求的一部分,向其他后端发出传出请求B和请求C,则请求B和请求C将默认使用与请求A相同的关键性。

过去,谷歌的许多系统都发展了自己的临时关键性概念,这些概念通常在服务之间不兼容。通过将关键性标准化并传播为 RPC 系统的一部分,我们现在能够在特定点一致地设置关键性。这意味着我们可以确信,过载的依赖项在拒绝流量时将遵守所需的高级关键性,无论它们在 RPC 堆栈的深度有多深。因此,我们的做法是将关键性设置为尽可能接近浏览器或移动客户端(通常在生成要返回的 HTML 的 HTTP 前端中),并且仅在堆栈中的特定点有意义的特定情况下覆盖关键性。

使用信号

我们的任务级过载保护的实现是基于利用率的概念。在许多情况下,利用率只是 CPU 速率的度量(即当前 CPU 速率除以为任务保留的 CPU 总数),但在某些情况下,我们还会考虑一些度量,例如为该任务保留的内存部分。目前正在使用中。当利用率接近配置的阈值时,我们开始根据请求的关键性拒绝请求(关键性越高,阈值越高)。

我们使用的利用率信号基于任务的本地状态(因为信号的目标是保护任务),并且我们有各种信号的实现。最普遍有用的信号基于进程中的“负载”,这是使用我们称为执行器负载平均值的系统确定的。

为了找到执行程序的平均负载,我们计算进程中活动线程的数量。在这种情况下,“活动”是指当前正在运行或准备运行并等待空闲处理器的线程。我们通过指数衰减来平滑该值,并随着活动线程数量的增长超出任务可用的处理器数量而开始拒绝请求。这意味着具有非常大扇出的传入请求(即安排大量短期操作的突发)将导致负载非常短暂地出现峰值,但平滑将大部分吞掉该峰值。但是,如果操作不是短暂的(即负载增加并在很长一段时间内保持较高水平),任务将开始拒绝请求。

虽然执行器负载平均值已被证明是一个非常有用的信号,但我们的系统可以插入特定后端可能需要的任何利用率信号。例如,我们可以使用内存压力(指示后端任务中的内存使用量是否超出正常操作参数)作为另一个可能的利用率信号。该系统还可以配置为组合多个信号并拒绝超过组合(或单独)目标利用率阈值的请求。

处理过载错误

除了优雅地处理负载之外,我们还深入思考客户端在收到与负载相关的错误响应时应如何反应。对于过载错误,我们区分两种可能的情况。

数据中心中的大部分后端任务都超载。

如果跨数据中心负载平衡系统工作完美(即,它可以传播状态并对流量变化立即做出反应),则不会发生这种情况。

数据中心中一小部分后端任务超载。

这种情况通常是由数据中心内部负载平衡的缺陷引起的。例如,某个任务最近可能收到了一个非常昂贵的请求。在这种情况下,数据中心很可能有其他任务的剩余容量来处理请求。

如果数据中心中的大部分后端任务超载,则不应重试请求,并且错误应一直冒泡到调用者(例如,向最终用户返回错误)。更典型的是,只有一小部分任务变得过载,在这种情况下,首选响应是立即重试请求。一般来说,我们的跨数据中心负载平衡系统尝试将流量从客户端引导到最近的可用后端数据中心。在少数情况下,最近的数据中心距离很远(例如,客户端可能在不同的大陆拥有最近的可用后端),但我们通常设法将客户端放置在靠近其后端的位置。这样,重试请求的额外延迟(只需几次网络往返)往往可以忽略不计。

从我们的负载均衡策略的角度来看,请求的重试与新请求没有区别。也就是说,我们不使用任何显式逻辑来确保重试实际上转到不同的后端任务;我们只是依赖于重试落在不同后端任务上的可能概率,这仅仅取决于子集中参与后端的数量。确保所有重试实际上都执行不同的任务会导致我们的 API 变得更加复杂,这是不值得的。

即使后端只是轻微过载,如果后端平等且快速地拒绝重试和新请求,则通常可以更好地服务客户端请求。然后可以立即在可能具有空闲资源的不同后端任务上重试这些请求。在后端以相同方式处理重试和新请求的结果是,在不同任务中重试请求成为有机负载平衡的一种形式:它将负载重定向到可能更适合这些请求的任务。

决定重试

当客户端收到“任务过载”错误响应时,需要决定是否重试请求。当集群中的大部分任务过载时,我们有一些机制来避免重试。

首先,我们实施最多三次尝试的每个请求重试预算。如果请求已经失败了 3 次,我们会让失败的消息冒泡给调用者。其基本原理是,如果请求已经到达超载任务三次,则再次尝试该请求相对不太可能有帮助,因为整个数据中心可能已超载。

其次,我们实施每个客户端的重试预算。每个客户端都会跟踪与重试相对应的请求比率。仅当该比率低于 10% 时才会重试请求。其基本原理是,如果只有一小部分任务超载,则相对而言不需要重试。

作为(最坏情况)的具体示例,我们假设数据中心接受少量请求并拒绝大部分请求。令X为根据客户端逻辑对数据中心尝试请求的总速率。由于将发生重试次数,请求数量将显着增加,达到略低于3X的程度。尽管我们已经有效地限制了重试造成的增长,但请求数量的三倍增长还是很显着的,尤其是在拒绝与处理请求的成本相当大的情况下。然而,在一般情况下,对每个客户端重试预算(10% 的重试率)进行分层可将增长降低至仅 1.1 倍,这是一个显着的改进。

第三种方法是让客户端在请求元数据中包含一个计数器,用于记录请求已被尝试的次数。例如,计数器在第一次尝试时从 0 开始,并在每次重试时递增,直到达到 2,此时每个请求的预算会导致其停止重试。后端保留最近历史记录中这些值的直方图。当后端需要拒绝请求时,它会查阅这些直方图来确定其他后端任务也过载的可能性。如果这些直方图显示大量重试(表明其他后端任务也可能超载),它们将返回“超载;请勿重试”错误响应,而不是触发重试的标准“任务超载”错误。

图 21-1显示了在各种示例情况下给定后端任务在滑动窗口内收到的每个请求的尝试次数(对应于 1,000 个初始请求,不计算重试)。为了简单起见,每个客户端的重试预算被忽略(即,这些数字假设重试的唯一限制是每个请求三次尝试的重试预算),并且子集化可能会稍微改变这些数字。

各种条件下的尝试直方图

图 21-1。各种条件下的尝试直方图

我们较大的服务往往是深层次的系统,而这些系统又可能相互依赖。在此架构中,请求只能在拒绝请求的层之上的层重试。当我们决定给定的请求无法得到服务并且不应重试时,我们使用“过载;不要重试”错误,从而避免组合重试爆炸。

考虑图 21-2中的示例(实际上,我们的堆栈通常要复杂得多)。想象一下数据库前端当前过载并拒绝请求。在这种情况下:

  • 然后,后端 B 将根据前面的准则重试该请求。
  • 然而,一旦后端 B 确定无法服务到数据库前端的请求(例如,因为该请求已尝试并被拒绝了 3 次),则后端 B 必须向后端 A 返回“过载;不要” t retry”错误或降级响应(假设即使对数据库前端的请求失败,它也可以产生一些适度有用的响应)。
  • 后端 A 对于从前端收到的请求具有完全相同的选项,并相应地继续处理。

一堆依赖关系。

图 21-2。依赖堆栈

关键点是,来自数据库前端的失败请求只能由紧邻其上方的后端 B 重试。如果重试多层,我们就会发生组合爆炸。

从连接加载

与连接相关的负载是最后一个值得一提的因素。我们有时只考虑后端直接由它们收到的请求引起的负载(这是基于每秒查询的负载建模方法的问题之一)。然而,这样做忽略了维护大量连接池的 CPU 和内存成本或快速连接流失的成本。这些问题在小型系统中可以忽略不计,但在运行非常大规模的 RPC 系统时很快就会成为问题。

如前所述,我们的 RPC 协议要求不活动的客户端执行定期健康检查。连接空闲一段可配置的时间后,客户端会断开其 TCP 连接并切换到 UDP 进行运行状况检查。不幸的是,当您有大量发出极低请求率的客户端任务时,这种行为就会出现问题:对连接进行运行状况检查可能需要比实际处理请求更多的资源。仔细调整连接参数(例如,显着降低健康检查的频率)或什至动态创建和销毁连接等方法可以显着改善这种情况。

处理新连接请求的突发是第二个(但相关的)问题。我们已经看到这种类型的突发发生在非常大的批处理作业的情况下,这些作业一次创建大量的工作客户端任务。同时协商和维护过多新连接的需要很容易使一组后端过载。根据我们的经验,有一些策略可以帮助减轻这种负载:

  • 将负载暴露给跨数据中心负载平衡算法(例如,根据集群的利用率,而不仅仅是请求数量来进行基本负载平衡)。在这种情况下,来自请求的负载被有效地重新平衡到具有备用容量的其他数据中心。
  • 强制批处理客户端作业使用一组单独的批处理代理后端任务,这些任务除了将请求转发到底层后端并以受控方式将其响应传回客户端外,什么也不做。因此,您拥有的是“批量客户端→批量代理→后端”,而不是“批量客户端→后端”。在这种情况下,当非常大的作业启动时,只有批处理代理作业受到影响,从而屏蔽了实际的后端(和更高优先级的客户端)。实际上,批处理代理就像保险丝一样。使用代理的另一个优点是,它通常会减少针对后端的连接数量,这可以改善针对后端的负载平衡(例如,代理任务可以使用更大的子集,并且可能更好地了解后端的状态)任务)。

结论

本章和数据中心的负载平衡讨论了各种技术(确定性子集、加权循环、客户端限制、客户配额等)如何帮助将负载相对均匀地分布在数据中心的任务上。然而,这些机制取决于分布式系统上的状态传播。虽然它们在一般情况下表现得相当好,但实际应用会导致少数情况下它们工作不完美。

因此,我们认为确保单个任务免遭过载至关重要。简而言之:配置为服务特定流量速率的后端任务应该继续以该速率服务流量,而不会对延迟产生任何重大影响,无论任务中抛出多少过量流量。因此,后端任务不应在负载下崩溃或崩溃。这些陈述应该在一定的流量速率下成立——高于任务要处理的流量的2 倍甚至10 倍。我们承认,系统可能会在某个点开始崩溃,并且提高发生这种崩溃的阈值变得相对难以实现。

关键是要认真对待这些退化情况。当忽略这些退化条件时,许多系统将表现出可怕的行为。随着工作量的增加,任务最终会耗尽内存并崩溃(或者最终在内存抖动中烧毁几乎所有的 CPU),由于流量下降和任务争夺资源,延迟会受到影响。如果不加以控制,系统子集(例如单个后端任务)中的故障可能会触发其他系统组件的故障,从而可能导致整个系统(或相当大的子集)发生故障。这种级联故障的影响可能非常严重,对于任何大规模运行的系统来说,防范此类故障至关重要;请参阅解决级联故障

假设过载的后端应该关闭并停止接受所有流量是一个常见的错误。然而,这种假设实际上与鲁棒负载均衡的目标背道而驰。我们实际上希望后端继续接受尽可能多的流量,但仅在容量释放时接受该负载。一个表现良好的后端,在强大的负载平衡策略的支持下,应该只接受它可以处理的请求,并优雅地拒绝其余的请求。

虽然我们拥有大量工具来实现良好的负载平衡和过载保护,但没有灵丹妙药:负载平衡通常需要深入了解系统及其请求的语义。本章描述的技术随着谷歌许多系统的需求而发展,并且可能会随着我们系统性质的不断变化而继续发展。

106例如,参见Doorman,它提供了一个协作式分布式客户端节流系统。

SRE(Site Reliability Engineering)是一种将软件工程和运维运营原则相结合的实践方法,旨在提高系统的可靠性、可扩展性和可维护性。Rancher是一个开源的容器管理平台,可以帮助用户轻松部署和管理容器化应用。根据引用\[1\],可以使用以下命令在Docker容器中部署Rancher: docker run -d --restart=unless-stopped --name=myrancher -p 18080:80 -p 18443:443 --privileged -v /var/server/rancher:/var/lib/rancher -v /var/rancher/log:/var/log/log rancher/rancher:stable 需要注意的是,根据引用\[2\],在安装Rancher时需要确保系统软件环境与Rancher的环境矩阵要求匹配。可以通过查看容器启动日志(使用docker logs -f rancher命令)来确认Rancher是否成功启动。一旦启动成功,就可以通过浏览器访问Rancher的UI界面。 另外,根据引用\[3\],还可以使用kubectl命令来导入Rancher的配置文件,例如: kubectl apply -f https://rancher.kkk.cn/v3/import/q9zxs2hp6j2d8hvfpw5trsf5wzz8lhhbffd8m74qvdm6rrjpg5mzr7_c-m-k7lq9m4t.yaml 这样可以更灵活地管理Rancher的配置。 #### 引用[.reference_title] - *1* *3* [Rancher部署](https://blog.csdn.net/u012824078/article/details/128084046)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [rancher 部署](https://blog.csdn.net/ljx1528/article/details/126418845)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值