负载卸除策略:如何避免系统过载并提升服务可用性

SEO 摘要: 本文介绍如何通过负载卸除避免系统过载,重点讨论过载反馈循环、有效吞吐量、客户端超时、负载测试、可见性、自动扩展、请求优先级、队列监控和分层过载保护等实践,帮助团队在高并发场景下保持服务稳定性和可用性。

负载卸除策略:如何避免系统过载并提升服务可用性

简介

我曾在海外某些大型科技公司的“服务框架”团队工作多年。这个团队负责开发多种工具,帮助内部服务负责人更快地构建服务,也帮助调用这些服务的客户端更轻松地集成服务。

其他团队会为服务负责人提供各种通用能力,例如计量、身份验证、监控、客户端库生成和文档生成。各个服务团队不需要手动把这些能力分别集成到自己的服务中,而是由“服务框架”团队统一完成一次集成,再通过配置方式将这些能力暴露给不同服务使用。

我们面对的一个挑战,是如何为服务提供合理的默认值,尤其是与性能和可用性相关的功能。例如,我们很难为客户端超时设置一个通用默认值,因为框架本身并不知道每个 API 调用的延迟特征。服务负责人或客户端自己也未必清楚这些值应该如何设置。因此,我们不断尝试,并在过程中获得了一些有价值的经验。

我们努力解决的一个常见问题,是如何确定服务器默认允许同时向客户端开放多少连接。这个设置的目标,是防止服务器承担过多工作而过载。更具体地说,我们希望将服务器的最大连接数设置,与负载均衡器允许的最大连接数配置相匹配。当时,现代弹性负载均衡服务还没有广泛出现,许多团队仍在使用硬件负载均衡器。

我们试图帮助服务负责人和服务客户端弄清楚两个值:一个是负载均衡器上的最大连接数应该设置为多少,另一个是我们提供的服务框架中对应的最大连接数应该设置为多少。我们当时的判断是:如果能明确人类工程师如何做出这个选择,就可以编写软件来模仿这种判断。

但要确定理想值,最终被证明非常困难。最大连接数设置得太低时,即使服务本身仍有充足能力,负载均衡器也可能会阻止更多请求进入。最大连接数设置得太高时,服务器又可能变得缓慢、无响应。如果最大连接数刚好适合当前工作负载,一旦工作负载发生变化,或者依赖项性能发生变化,这个值又会变得不合适,进而造成不必要的中断或过载。

最后,我们发现,“最大连接数”这个概念本身并不够准确,无法完整解决系统过载问题。本文将介绍一些我们认为更有效的方法,其中最重要的就是负载卸除。通过负载卸除、过载保护、有效吞吐量控制和分层防护机制,服务可以在高并发和突发流量下保持更稳定的性能与可用性。对于希望把这类稳定性建设落到日常研发流程中的团队,也可以借助 PingCode 这类智能化研发管理工具,将目标制定、需求评审、开发、测试、发布上线、缺陷跟踪和 Wiki 复盘沉淀串联起来,让过载治理和服务可用性改进更加数据化、自动化和可追踪。

过载问题剖析:延迟如何影响有效吞吐量

在大型系统中,团队通常会设计系统,使其在遭遇过载之前就主动扩展,以尽量避免过载。但系统保护需要分层完成。自动扩展只是第一层,除此之外,还需要有条不紊地卸除超限负载的机制、监控这些机制的能力,以及持续测试能力。其中,测试尤其重要。

在对服务进行负载测试时,我们发现服务器在低使用率下的延迟通常低于高使用率下的延迟。随着负载加重,线程争用、上下文切换、垃圾回收和 I/O 争用等问题会变得更加明显。最终,服务会到达一个转折点,从这个点开始,性能会更加急剧地下降。

这一现象背后的理论被称为“通用可伸缩定律”,它衍生自阿姆达尔定律。该理论指出,虽然并行化可以提升系统吞吐量,但系统吞吐量最终会受到串行化点吞吐量的限制,也就是那些无法并行化的任务会成为瓶颈。

更麻烦的是,系统吞吐量不仅受系统资源限制,还常常会在过载时下降。如果系统承担的工作超过资源所能支撑的范围,系统就会变慢。计算机即使处于过载状态,也仍然会尝试处理工作,但会花费更多时间进行上下文切换,并整体变得缓慢,最终表现为不可用。

在客户端与服务器通信的分布式系统中,客户端通常只会等待一段时间。如果超过这段时间仍然没有得到响应,客户端就会停止等待。这个等待时间称为超时时间。如果服务器因为过载导致延迟超过客户端超时时间,请求就会开始失败。

当服务器响应时间随着提交吞吐量增加而上升时,系统最终会到达一个迅速恶化的转折点。在这个点之后,即使继续增加请求量,也无法带来更多有效处理,反而会让系统更慢、更不稳定。

当响应时间超过客户端超时时间后,问题会非常明显:客户端开始感知到失败。但这还没有完全说明问题有多严重。为了更清楚地理解这一点,可以同时观察客户端感知到的可用性。

如果使用中位数响应时间来衡量延迟,就能看到一个关键现象:中位数响应时间表示 50% 的请求比这个值更快。如果服务的中位数延迟等于客户端超时时间,就意味着有一半请求已经超时,因此可用性只有 50%。在这里,延迟问题已经转化成了可用性问题。

描述可用性问题时,一个更清晰的方式,是区分“提交吞吐量”和“有效吞吐量”。

提交吞吐量是指每秒发送到服务器的请求总数。有效吞吐量则是其中成功完成、没有产生错误,并且延迟足够低、客户端仍能使用响应结果的那部分请求。

正反馈循环:系统过载如何自我放大

过载最危险的地方之一,是它会在反馈循环中不断放大自身影响。

客户端请求超时,本身已经很糟糕。更糟糕的是,在超时之前,服务器已经在这个请求上消耗的工作量会全部变成无效工作。系统在过载时最不应该做的事情,就是浪费工作,因为此时系统能力本来就已经受限。

更糟糕的是,客户端通常会重试请求。这样会成倍增加提交给系统的负载。如果一个面向服务的架构中调用链很深,例如客户端调用一个服务,这个服务又调用其他服务,而其他服务还会继续调用下游服务,并且每一层都会执行多次重试,那么底层服务出现过载时,重试可能会逐层放大,最终以指数级方式增加系统负载。

当这些因素叠加在一起时,过载就会形成自己的正反馈循环,使系统进入一种持续过载的稳定状态。

通过负载卸除防止浪费工作

从表面上看,负载卸除似乎很简单:当服务器即将过载时,它应该开始拒绝超出能力范围的请求,把资源集中用于处理已经决定接受的请求。

负载卸除的目标,是确保服务器接受的请求能够保持足够低的延迟,从而在客户端超时之前返回响应。通过这种方式,服务器可以对自己接受的请求保持较高可用性,而受影响的只是超限流量。

通过卸除超限负载来控制延迟,可以提高系统可用性。不过,从整体可用性曲线上看,这种收益有时并不直观。总体可用性仍然可能下降,看起来依然不好。但关键在于:服务器决定接受的请求仍然是可用的,因为它们能快速得到处理。

负载卸除可以让服务器保持有效吞吐量,并在提交吞吐量继续增加时,尽可能多地完成请求。不过,负载卸除本身也不是零成本操作。因此,服务器最终仍会受到阿姆达尔定律的限制,有效吞吐量也可能在极端负载下下降。

通过负载测试验证过载保护

当我和其他工程师讨论负载卸除时,我通常会强调:如果他们在负载测试中没有让服务真正接近甚至远远超过故障点,就应该假设服务会以最不理想的方式失败。

在大型系统中,团队通常会花大量时间进行负载测试。通过绘制类似前文描述的曲线,团队可以确定系统在过载状态下的性能基线,并跟踪服务变更之后的表现。

负载测试有很多类型。有些负载测试用于验证队列是否会随着负载增加而自动扩展;另一些测试则会使用固定队列大小。在过载测试中,如果服务可用性随着吞吐量增加而迅速下降到 0,这通常说明服务需要额外的负载卸除机制。

理想的负载测试结果是:当服务接近充分使用状态时,有效吞吐量趋于稳定;即使继续施加更大的提交吞吐量,有效吞吐量也能保持相对稳定。

混沌工程工具也可以帮助测试服务。例如,它们可以制造 CPU 过载,或引发数据包丢失,以模拟过载期间可能出现的状况。另一种测试方法是选择一个现有的负载生成测试,也就是所谓的“金丝雀”,持续向测试环境施加固定负载,而不是不断增加负载,然后逐步从测试环境中移除服务器。这样,每个实例承受的提交吞吐量会增加,从而可以测试单个实例的吞吐能力。

这种通过减小实例池规模来人为增加负载的方法,对于单独测试服务很有用,但不能完全替代满载测试。端到端满载测试还会增加该服务依赖的其他服务的负载,从而发现更深层的瓶颈。

在测试期间,除了测量服务器端的可用性和延迟之外,还应确保测量客户端感知到的可用性和延迟。团队需要在客户端可用性开始下降后,继续将负载增加到远超转折点的程度。如果负载卸除机制起作用,即使提交吞吐量远远超过服务已扩展出来的能力,有效吞吐量也应保持稳定。

在引入用于避免过载的机制之前,进行过载测试非常重要。每一种机制都会增加复杂性。例如,本文开头提到的服务框架中有很多配置项,而为这些配置项确定合适默认值非常困难。用于避免过载的每种机制都能提供不同形式的保护,但效果都有边界。通过测试,团队可以发现系统瓶颈,并确定处理过载所需的保护机制组合。

建立负载卸除与过载保护的可见性

无论使用哪种方法来防止服务过载,团队都需要仔细思考需要哪些指标和可见性,才能确认这些过载保护机制确实生效。

当过载保护拒绝请求时,这种拒绝行为会降低服务对外表现出的可用性。如果服务判断错误,在自己仍有能力处理请求时就拒绝请求,例如最大连接数设置得过低,那么它就产生了误判。团队通常会努力使这类误判率接近于零。

如果团队发现某个服务的误判率经常不为零,可能说明服务调得过于敏感,也可能说明个别主机确实经常出现过载,并且存在扩展或负载均衡问题。在这种情况下,团队可能需要调整应用程序性能,或者改用更大的实例规格,以便更从容地处理负载不均衡问题。

从可见性角度看,当负载卸除拒绝请求时,团队需要有合适工具了解请求来自哪个客户端、调用的是哪个操作,以及其他有助于调整保护机制的信息。团队也会使用告警检测过载保护是否正在拒绝大量流量。一旦出现明显的负载卸除,首先要做的通常是增加容量,并解决当前瓶颈。

围绕负载卸除的可见性,还有一个细小但重要的注意事项:不要让失败请求的低延迟扭曲服务的整体延迟指标。

被负载卸除的请求通常会非常快地失败,其延迟远低于正常处理的请求。例如,如果一个服务卸除了 60% 的流量,它的中位数延迟看起来可能非常漂亮,即使成功请求的延迟已经非常糟糕。原因是大量快速失败请求拉低了整体延迟指标。因此,团队应将被拒绝请求的延迟与成功请求的延迟分开观察。

负载卸除对自动扩展和可用区故障的影响

配置不当的负载卸除机制,可能会阻止反应式自动扩展发挥作用。

设想一个服务同时采用两种机制:一种是基于 CPU 的反应式自动扩展,另一种是在达到类似 CPU 目标时开始拒绝请求的负载卸除机制。在这种情况下,负载卸除系统会减少进入服务的请求数量,使 CPU 保持在较低水平。结果是,自动扩展系统永远收不到“CPU 已经升高、需要启动新实例”的信号。

在设置用于应对可用区故障的自动扩展限制时,也需要仔细考虑负载卸除逻辑。服务通常应扩展到这样一个程度:即使某个可用区的服务能力不可用,剩余能力仍能在满足延迟目标的前提下承载负载。

工程团队经常会观察 CPU 等系统指标,估算服务距离容量上限还有多远。但如果存在负载卸除机制,系统队列在运行时可能已经接近请求将被拒绝的状态,而 CPU 等系统指标未必能准确反映这一点。这样一来,服务可能并没有预先配置足够的额外容量来处理可用区故障。因此,在使用负载卸除机制时,还需要额外通过破坏性测试确认服务在任意时刻到底有多少队列容量和安全余量。

实际上,负载卸除也可以用于对非高峰、非关键流量进行流量整形,从而节约成本。例如,某些处理网站流量的服务可能会判断,搜索爬虫流量不值得为其扩展能力,以实现完整的可用区冗余。

但这种方法必须非常谨慎。并非所有请求的处理成本都相同。要证明一个服务应该同时为人工访问流量和被卸除的超限爬虫流量提供可用区级冗余,需要仔细设计、持续测试,并获得业务认可。

如果服务客户端不知道该服务以这种方式配置,那么在可用区发生故障时,它看到的行为可能像是关键可用性大幅下降,而不是系统正在卸除非关键负载。因此,在面向服务的架构中,团队通常会尽可能早地推进这类流量整形,例如在最早接收客户端请求的服务中完成,而不是试图在整个调用栈中做全局优先级决策。

负载卸除机制:如何管理服务过载

在讨论负载卸除和不可预测事件时,也需要关注许多会导致流量被拒绝的可预测场景。

在成熟的大型系统中,服务通常会保持足够的额外能力,以便在不增加更多容量的情况下处理可用区故障。它们也会使用限流模式,在不同客户端之间确保公平性。

然而,即使有这些保护措施和工程实践,服务在任意时刻的能力仍然是有限的,因此仍可能因为各种原因而过载。这些原因包括:流量意外激增;错误部署或其他情况导致队列容量突然下降;客户端从发起低成本请求,例如缓存读取,转为发起高成本请求,例如缓存未命中或写入。

当服务过载时,它必须尽力完成已经接受的请求。也就是说,服务必须防止自己因为继续接收过多请求而彻底崩溃。下面将讨论一些注意事项,以及用于管理过载的常见方法。

了解丢弃请求的代价

团队需要确保负载测试的强度远远超过有效吞吐量趋于稳定的点。这样做的一个关键原因,是确保系统在负载卸除过程中丢弃请求的成本尽可能低。

实践中,很容易漏掉一些意外成本,例如多余的日志语句、套接字设置或其他处理逻辑。这些细节可能让丢弃请求的代价远高于预期。

在极少数情况下,快速丢弃请求的成本可能高于继续处理请求。在这类场景中,可以故意放慢被拒请求的返回速度,使其至少接近成功响应的延迟。但只有在继续处理这类请求的成本足够低时,才应该这样做。例如,当请求尚未绑定应用程序线程时,这种做法会更安全。

确定请求优先级

当服务器过载时,它有机会对传入请求进行分类,决定接受哪些请求、拒绝哪些请求。

服务器最重要的请求之一,是来自负载均衡器的健康检查请求。如果服务器无法及时响应健康检查,负载均衡器可能会在一段时间内停止向该服务器发送新请求,导致该服务器处于空闲状态。在过载情况下,最不应该做的事情,就是无意中缩小可用服务池。

除健康检查请求之外,请求优先级通常取决于具体服务场景。

设想一个为电商网站渲染页面提供数据的服务。如果某个服务调用是为了支持搜索索引爬虫渲染页面,它的重要性可能低于来自真实用户的请求。处理爬虫请求很重要,但更理想的做法,是将这些请求转移到非高峰时段处理。

然而,在许多服务相互协作的复杂环境中,如果不同服务使用相互冲突的优先级启发式规则,就可能影响整个系统的可用性,并浪费已经完成的工作。

优先级也可以和限流结合使用,以避免严格的硬上限,同时仍然防止服务过载。例如,当允许客户端突破已配置限制时,超限请求的优先级可以低于其他客户端未超限请求的优先级。

团队通常需要投入大量时间研究调度和放置算法,以最大限度降低突发容量不可用的风险。但在权衡时,系统通常应更偏向可预测、预先配置好的工作负载,而不是不可预测的突发工作负载。

通过客户端超时控制减少无效工作

如果服务器在处理请求过程中发现客户端已经超时,就可以停止剩余工作,并让该请求失败。否则,服务器会继续处理请求,而这个迟来的响应对客户端来说已经没有意义。

从服务器角度看,它可能成功返回了响应。但从已经超时的客户端角度看,这个请求已经失败了。

避免这类工作浪费的一种方法,是让客户端在每个请求中包含超时提示,告诉服务器客户端愿意等待多长时间。服务器可以评估这些提示,并以很低成本丢弃注定会失败的请求。

超时提示可以用绝对时间表示,也可以用持续时间表示。

不过,分布式系统中的服务器很难对“当前时间”达成完全一致。因此,许多云环境会提供时间同步能力,将计算实例的时钟与高精度、冗余的时间源同步。正确同步的时钟对日志记录也非常重要。比较两台时钟不同步的服务器上的日志,会让故障排查变得更加困难。

“关注时间”的另一种方法,是在单台机器上测量持续时间。服务器通常很擅长在本地测量已经经过的时间,因为它不需要与其他服务器达成一致。但用持续时间表达超时也有问题。

例如,在服务器使用网络时间协议同步时,所使用的计时器必须是单调计时器,不能倒退。另一个更困难的问题是:为了测量持续时间,服务器必须知道何时启动计时器。在极端过载情况下,大量请求可能在 TCP 缓冲区中排队。等服务器终于从缓冲区中读取这些请求时,客户端可能早已超时。

当系统表达客户端超时提示时,应尽量以可传递的方式应用这些提示。在面向服务架构包含多个调用跳点时,团队会在每一跳之间传播“剩余时间”或最终截止时间,使调用链末尾的下游服务也知道自己还有多少时间可以让响应产生实际价值。

当服务器知道客户端截止时间后,接下来要回答的问题是:应该在服务实现的哪个位置强制执行这个截止时间?

如果服务中有请求队列,可以在每个请求出队后估算是否已经超时。但这仍然很复杂,因为系统并不知道这个请求接下来要花多长时间。

某些系统会保存 API 请求所需时间的估算值。如果客户端报告的剩余时间小于延迟估算值,系统就会提前丢弃请求。但事情很少这么简单。例如,缓存命中会比缓存未命中更快,但估算器事先并不知道请求会命中还是未命中。又或者,服务的后端资源被分区,只有部分分区很慢。

这些场景中有很多可以优化的空间,但这些优化也可能在不可预测情况下产生反效果。

根据经验,在服务器端强制执行客户端超时虽然复杂,也需要做权衡,但仍然比其他选择更好。强制执行“每个请求的生存时间”,并丢弃注定失败的请求,通常比让请求不断积压、让服务器继续处理对任何一方都已经没有意义的请求更好。

完成已经开始的工作

在过载情况下,团队尤其不希望浪费任何有价值的工作。丢弃已经进行到一半的工作,会造成更强的正反馈循环,因为客户端在服务未及时响应时通常会重试请求。

这时,一个已经消耗资源的请求可能会变成多个消耗资源的请求,从而成倍增加服务负载。客户端超时并重试时,通常会停止等待第一个连接上的响应,同时在另一个连接上发起新的请求。如果服务器完成了第一个请求并返回响应,客户端也可能已经不再接收该响应,因为它此时正在等待重试请求的响应。

为了解决这类工作浪费问题,服务应尽量设计为执行有边界的工作。

对于可能返回大型数据集,或者任何列表型结果的 API,团队通常会将其设计为分页 API。这些 API 返回部分结果和一个令牌,客户端可以使用该令牌请求更多数据。实践表明,当服务器处理的请求在内存、CPU 和网络带宽方面都有明确上限时,服务更容易估算额外负载。如果服务器完全不知道处理一个请求需要多少资源,准入控制就会变得非常困难。

围绕客户端如何使用服务 API,也可以发现一些与请求优先级相关的更细微机会。

例如,假设某个服务有两个 API:start()end()。客户端只有同时能够调用这两个 API,才能完成一项工作。在这种情况下,服务应该让 end() 请求优先于 start() 请求。如果优先处理 start() 请求,客户端可能会启动更多工作,但无法完成已经开始的工作,从而造成更多资源浪费。

分页也是观察工作浪费的一个重要场景。如果客户端必须连续发起多个请求才能遍历服务结果,而在第 N-1 页之后请求失败并丢弃结果,那么它就浪费了前面 N-2 次服务调用,以及过程中执行的所有重试。

这说明,和 end() 请求类似,第一页请求的优先级应低于后续分页请求。这也进一步说明,服务应设计为执行有边界的工作,并且不要在同步操作中无限制地分页调用下游服务。

监控队列:识别请求积压与过载风险

在管理内部队列时,观察请求在队列中停留的时间非常有帮助。

许多现代服务架构都会使用内存队列连接不同线程池,以便在工作流的不同阶段处理请求。带执行器的 Web 服务框架前面可能配置有队列。对于任何基于 TCP 的服务,操作系统都会为每个套接字维护缓冲区,而这些缓冲区中可能包含大量积压请求。

当系统从队列中取出工作时,应利用这个机会查看该工作在队列中停留了多久。至少,团队应该将这个持续时间记录到服务指标中。

除了限制队列大小之外,团队还发现,对传入请求在队列中停留的时间设置上限非常重要,并且应丢弃过旧请求。这可以释放服务器资源,让它处理成功机会更高的新请求。

这种方法的更极端版本,是在协议支持的前提下改用后进先出(LIFO)队列。例如,某些旧协议管道机制并不支持 LIFO 队列,但一些较新的协议通常可以更好地支持这类行为。

在服务过载时,负载均衡器也可能使用某种“波动队列”功能,将传入请求或连接排队。这类队列可能导致连锁问题,因为当服务器最终收到请求时,它并不知道该请求已经在队列中等待了多久。

通常,更安全的默认做法是使用溢出配置:让超限请求快速失败,而不是继续排队。现代负载均衡服务往往也会采用这种思路,即拒绝超限流量,而不是无限制地堆积请求。无论采用何种配置,团队都应监控与负载均衡器相关的指标,例如波动队列深度或溢出计数。

根据经验,监控队列的重要性怎么强调都不为过。在许多系统和库中,工程师经常会惊讶地发现,自己直觉上认为不存在队列的地方,其实存在内存队列。深入研究系统时,一个有用假设是:在自己尚未发现的地方,很可能还存在某个队列。

当然,如果能设计出符合实际的测试用例,过载测试通常比单纯阅读代码更有价值。

在下层防止过载

服务由多个层次组成,从负载均衡器,到带有网络过滤和防火墙能力的操作系统,再到服务框架,最后到业务代码。每一层都可以提供保护服务的能力。

常见 HTTP 代理通常支持最大连接数配置,例如限制传递给后端服务器的活动请求或连接数量。这可能是一个有用机制,但更适合作为最后手段,而不是默认保护选项。使用代理做保护时,很难准确区分重要流量和非重要流量;而且,原始进行中请求计数有时也无法准确反映服务是否真的过载。

本文开头提到,我曾在“服务框架”团队遇到一个挑战:我们试图向内部团队提供负载均衡器最大连接数的默认配置建议。最终,我们建议团队将负载均衡器和代理上的最大连接数设置得相对较高,并允许服务器利用本地信息实现更准确的负载卸除算法。

但同样重要的是,最大连接数也不能超过服务器上的监听线程数、监听进程数或文件描述符数量,以确保服务器有足够资源处理来自负载均衡器的关键健康检查请求。

操作系统用于限制服务器资源使用的能力非常强大,在紧急情况下尤其有用。由于团队知道过载一定可能发生,因此应提前准备好运行手册和具体命令。某些系统工具可以为服务器接受的连接数量设置上限,并以低于应用层处理成本的方式拒绝超限连接。在这类故障准备和应急响应工作中,团队也可以使用 Worktile 这类通用项目协作系统统一管理任务、文档、IM 沟通、日历、甘特图、工时和审批流程,让运行手册维护、演练安排和故障复盘更清晰可控。

还可以配置更复杂的控制策略,例如限制新连接建立速率,甚至按照源 IP 地址限制连接速率或连接数量。源 IP 过滤功能很强大,但并不总是适用于传统负载均衡器。如果负载均衡器能够在网络层保留调用方源 IP,那么这类操作系统级规则就能按预期发挥作用。

层内保护:在不同系统层级卸除超限流量

在某些情况下,服务器可能已经耗尽资源,甚至无法在不变慢的情况下拒绝请求。考虑到这个现实,团队需要查看服务器与客户端之间的所有跳点,理解它们如何协同工作,帮助系统卸除超限负载。

例如,许多云服务默认都提供负载卸除选项。当前置 API 网关时,可以配置每个 API 能够接受的最大请求速率。当服务前面还有应用负载均衡、内容分发网络或 Web 防护层时,也可以在多个维度上卸除超限流量。

可见性带来了一个难以解决的矛盾。越早拒绝流量,丢弃超限流量的成本越低;但拒绝得越早,服务内部能看到的信息就越少,可见性也越差。

因此,团队通常会采用层内保护:允许服务器接受略高于自身处理能力的流量,然后由服务器根据本地信息丢弃超限流量,并记录足够多的信息,以便了解它到底丢弃了什么流量。

由于服务器能够丢弃的流量本身也是有限的,因此还需要依靠服务器前面的层,防止服务器收到海量不可承受的流量。

换个角度理解过载

本文讨论了一个核心现实:当系统被分配了更多并行工作,同时资源限制、资源争用等因素开始出现时,系统会变慢。过载反馈循环由延迟驱动,最终导致工作浪费、请求速率上升,并进一步造成更多过载。

因此,在遇到过载时,关键是卸除超限负载,并保持可预测、稳定的性能。这样才能避开由通用可伸缩定律和阿姆达尔定律驱动的恶性循环。关注可预测、稳定的性能,是构建高可靠服务的重要设计原则。

例如,某些大规模云数据库服务会以可预测的性能和可用性为核心目标。即使工作负载激增并超过预配置资源,这类服务也会尽力保持可预测的有效吞吐量和延迟。自动扩展、自适应容量和按需扩容等能力,会快速响应并提高有效吞吐量,以适应增加的工作负载。在这个过程中,有效吞吐量保持稳定,也能帮助上层服务维持可预测性能,并提升整个系统的稳定性。

在关注可预测性能方面,函数计算服务提供了另一个范围更广的例子。当使用函数计算实现服务时,每个 API 调用都会分配到相对一致的计算资源,并运行在自己的执行环境中,而且该执行环境一次只处理一个请求。这不同于传统服务器模式,在传统模式中,一个服务器通常会同时处理多个 API 请求。

将每个 API 调用隔离在自己的资源环境中,包括计算、内存、磁盘和网络资源,可以在一定程度上绕过阿姆达尔定律,因为一个 API 调用的资源不会与另一个 API 调用竞争。因此,如果提交吞吐量超过有效吞吐量,有效吞吐量更可能保持稳定,而不是像传统服务器环境中那样下降。

这并不是万能方法,因为依赖项仍可能变慢,并导致并发量升高。但在这种情况下,至少本文讨论的主机级资源争用问题不会以同样方式出现。

资源隔离是现代无服务器计算环境的一个重要优点。这个优点并不总是显而易见,但非常关键。实践中,团队发现实现可靠的负载卸除需要大量工作,从调整线程池,到选择最适合负载均衡器最大连接数的配置,都需要仔细设计。

而这些配置通常很难找到合理默认值,因为它们高度依赖每个系统独特的运行特征。较新的无服务器计算环境通过底层资源隔离,并在上层提供限制和并发控制等能力,帮助团队防止过载。

从某种意义上说,团队不再需要追求某个理想的默认配置值,而是可以在架构层面避开这类复杂配置,从而在无需大量手动调优的情况下防止多种过载问题。

文章包含AI辅助创作:负载卸除策略:如何避免系统过载并提升服务可用性,发布者:shang,转载请注明出处:https://worktile.com/kb/p/3975171

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
shang的头像shang

发表回复

登录后才能评论
注册PingCode 在线客服
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部