Netflix按优先级减载保障可用性(一)

July 2, 2024

背景

Netflix的文章《Keeping Netflix Reliable Using Prioritized Load Shedding》探讨了Netflix如何通过引入基于优先级的减载法(Prioritized Load Shedding)来改进系统可用性,确保在系统高负载或故障时仍能保障基础功能可用。

减载(Load Shedding)是指当服务接近负载极限时,通过丢弃流量(dropping traffic)的方式主动丢弃 一部分负载。优雅降低(Graceful degradation)比 Load Shedding 更进一步:减少处理每个请求时所做的事,降低系统负载。

优先级的渐进式减载法

在接入层或者后端服务中,通常会有类似Hystrix的限流器来保障服务的可用性。但是限流器通常没有区分流量的优先级,而是让每个请求都按照统一的处理策略(包括流量控制、熔断降级以及过载保护等,参考sentinel-go源码分析

Netflix引入了基于优先级减载法,该方法的核心思想是根据请求的优先级来逐步限制流量,确保关键请求得到优先处理,而较低优先级的请求在必要时被丢弃

限流时机

当前(本文发表时间2020年)的限流措施集中在Netflix的接入层网关(Zuul)上,Zuul 可以在请求生命周期的两个时刻应用负载削减。

服务限制

Zuul 可以通过监控错误率和对该服务的并发请求来感知后端服务何时出现问题。这两个指标是故障和延迟的近似指标。当其中一个指标的阈值百分比超过时,我们会通过限制流量来减少服务的负载。

具体限流方案可以参考我之前介绍go-sentinel。

全局限制

另一种情况是 Zuul 本身出现问题。触发全局限制的关键指标是Zuul自身的 CPU 利用率、并发请求和连接数。当超过这些指标的任何阈值时,Zuul 将积极限制流量以在系统恢复期间保持自身正常运行。

💡
类似go-sentinel中的系统过载保护。避免网关服务自身负载过道导致的雪崩。

限流策略

请求分类

从三个维度对请求流量进行分类:吞吐量、功能性和关键性。根据这些特征,流量被分为以下几类::

  1. 非关键型请求(NON_CRITICAL):不影响播放或会员体验的请求,如日志和后台请求。
  2. 体验降级型请求(DEGRADED_EXPERIENCE):影响会员体验但不影响播放功能的请求,如停止和暂停标记、语言选择、观看历史记录等。
  3. 关键型请求(CRITICAL):直接影响播放功能的请求,如果失败会导致播放错误。

Netflix使用API网关服务(Zuul)来根据请求的属性将请求分类为 NON_CRITICAL、DEGRADED_EXPERIENCE 和 CRITICAL 存储桶(即上面的三个分类),并根据每个请求的各自特征计算 1 到 100 之间的优先级分数(priority score)。这些属性包括吞吐量、功能性和重要性。

大多数情况下,请求工作流会正常进行,无需考虑请求优先级。但是故障发生时(后端出现问题或 网关Zuul 本身出现问题),优先级较高的请求会得到优先处理,而优先级较低的请求则可能不会被处理。该实现类似于具有动态优先级阈值的优先级队列。这允许 Zuul 丢弃优先级分数低于当前优先级阈值的请求。

💡
Netflix按优先级减载能够实施的前提,是视频业务下能影响播放鉴权的请求占整体流量的比例是较少的。OTT的场景也类似,只要用户进入播放状态中,流量的大头都是转移到CDN的流量拉取了。业务服务端的请求比例(页面结构、观看历史、关注收藏、会员等请求)就非常小了。 当然在实际的业务场景中,流量本身也是有优先级的,比如详情页场景,视频列表的漏出远远比相关推荐来得重要。所以业务在接口设计上也可以考虑优先级分类,优先保障核心数据的下发,这样不管是在稳定性还是终端性能上都能有更好的效果。

流量管理策略

按优先级限制

负载过高时,我们会从最低优先级开始逐步减少流量。使用立方函数(Cubic function)来管理节流级别。确保在极端情况下也能保持服务的部分可用性。

上图是Cubic function应用的示例。随着过载百分比的增加(即节流阈值和最大容量之间的范围),优先级阈值会非常缓慢地落后于它:在 35% 时,它仍然在 95% 左右。如果系统继续降级,我们会在超过 80% 时达到优先级 50,然后在超过 95% 时最终达到优先级 10,依此类推。

💡
About Cubic function 许多年来,不同的流量控制算法已经在各种TCP堆栈中实现和使用。你可能听说过TCP上的一些术语,例如Cubic、Tahoe、Vegas、Reno、Westwood,以及最近流行的BBR等。这些都是TCP中使用的不同拥塞控制算法。这些算法的作用是决定发送方应该以多快的速度发送数据,并同时适应网络的变化。如果没有这些算法,我们的互联网一定会被数据填满并且崩溃。

这里提到的Cubic 是一种较为温和的拥塞算法,它使用三次函数作为其拥塞窗口的算法,并且使用函数拐点作为拥塞窗口的设置值。Linux内核在2.6.19后使用该算法作为默认TCP拥塞算法。我们今天所使用的绝大多数Linux 分发版本,例如Ubuntu、Amazon Linux 等均将Cubic作为缺省的 TCP流量控制的拥塞算法。

阻止重试风暴

当 Zuul 决定丢弃流量时,它会向终端设备发送信号,让终端知道我们需要它们退出重试。它通过指示它们可以执行多少次重试以及它们可以在什么样的时间窗口中执行重试来实现这一点。例如:

{ “maxRetries” : <max-retries>, “retryAfterSeconds”: <seconds> }

使用这种背压(Back Pressure)机制,我们可以比过去更快地阻止重试风暴。我们根据请求的优先级自动调整这两个拨盘。优先级较高的请求将比优先级较低的请求更积极地重试,从而提高流式传输的可用性。

请求优先级如何决定

如何确定某个请求的优先级分类,以及如何保障策略的长期有效。

请求的优先级分类

为了验证我们的请求分类假设,即特定请求是否属于 NON_CRITICAL、DEGRADED 或 CRITICAL 分类,我们需要一种方法来测试该请求失败时的用户体验。为了实现这一点,我们利用了内部故障注入工具 ( FIT ),并在 Zuul 中创建了一个故障注入点,使我们能够根据提供的优先级删除任何请求。这使我们能够通过阻止特定设备或成员的优先级范围来手动模拟负载删除体验,让我们了解哪些请求可以安全地删除而不会影响用户。

如何保障策略的长期有效

Netflix 拥有各种各样的客户端设备、客户端版本以及与系统交互的方式。为了确保在任何这些情况下限制非关键请求时不会给会员带来痛苦,我们利用了我们的基础设施实验平台ChAP

该平台允许我们进行 A/B 实验,将少数生产用户分配到对照组或实验组,持续 45 分钟,同时限制实验组的一系列优先级。这让我们能够捕获各种实时用例并衡量其对播放体验的影响。ChAP 分析成员的每台设备 KPI,以确定对照组和实验组之间是否存在偏差。

结语

  1. 何时限流(减载):在接入层网关Zuul上统一实施,有两种场景:后端服务异常(请求回包耗时或者超时等)、自身负载异常。另外在客户端也实施了背压策略,服务端异常时会下发指令,所以客户端网络库也要有类似的优先级减载策略,尽早阻止重试风暴。
  2. 限流策略:按流量分类计算优先级分数,分数低于限流阈值的命中限流策略。限流阈值会根据负载情况动态调整。
  3. 请求优先级如何确定:使用故障注入工具FIT模拟故障,根据终端对用户体验的影响来决定。同时考虑多多版本的差异性,在现网引入A/B实验,在实验组内限流,根据现网体验数据KPI,确定对照组和实验组的体验是否存在差异。

参考

查看全部文章查看全部文章