如何在基础架构从系统故障中自我恢复时,仍能确保观众能够在Netflix上观看他们最喜欢的节目? Netflix 给出了他们的解决方案。
作者 / Manuel Correa,Arthur Gonigberg,Daniel West
译者 / Alpha
原文链接/https://netflixtechblog.com/keeping-netflix-reliable-using-prioritized-load-shedding-6cc827b02f94
对世界各地的司机来说,堵车是最令人沮丧的经历之一。每个人都缓慢得像是在爬行一样,有时是因为出现了一个小问题,有时则是毫无理由的拥堵。作为Netflix的工程师,我们一直在不断评估如何重新设计流量管理框架。如果我们知道每个人行程的紧迫性,就可以有针对性地为他们选择路线,而不是让大家做无谓的等待,这么做的结果会如何呢?
在Netflix的工程设计中,我们的动力是确保Netflix可以在您需要的时候及时出现。然而,就在去年,我们的系统还很容易受到人们所说的的“交通拥堵”的影响;我们有开关断路器,但没有循序渐进的方式来减轻负载。为了改善会员的播放体验,我们引入了基于优先级的渐进式减载法。
下面的动画展示了当后端根据优先级限制流量时,观众体验到的Netflix性能。当较低优先级的请求被限制时,整个播放体验完整流畅,观众可以尽情享受观看的内容。接下来让我们深入研究一下我们是如何做到这一点的。
触发失败的诱因有很多,比如客户端行为不当引发的重试风暴、后台服务器规模不足、部署不当、网络故障或云提供商的问题等等。任何上述原因都可能瞬即给系统造成巨大的载荷,在以前,这些例子中的每一个都会使我们的会员无法播放视频。为了防止此类突发事件的发生,我们开始着手实现以下目标,使Netflix的服务更具弹性:
- 跨设备(移动、浏览器和电视)持续确定请求的优先级
- 根据优先级逐步限制请求
- 通过对特定优先级的请求进行紊乱测试(Chaos Testing)(有意的故障输入)来验证假设
我们设想的,包含优先级节流和紊乱测试的最终架构,如下所示。
具有优先级节流和紊乱测试的高级播放端架构
构建请求分类
我们决定聚焦于三个维度来对请求流量进行分类:吞吐量、功能性和重要性。根据这些特征,流量被分为以下几类:
- 非关键型请求:该类型流量不影响播放或会员体验,日志和后台请求就属于此类流量。此类请求通常有较高的吞吐量,是系统负载来源中占据比例较大的部分。
- 体验降级型请求:该类型流量影响会员体验,但不影响播放功能。此类请求流量常用于以下功能:停止和暂停标记、播放器中的语言选择、观看历史记录等。
- 关键型请求:流量影响播放功能。如果请求失败,会员在点击播放时会看到一条错误消息。
API网关服务(Zuul)根据请求的属性,将其分为非关键型请求、体验降级型请求和关键型请求,并根据每个请求的单独特征,计算它们从1到100之间的优先级分数。计算是任务开始的第一步,以便在其余关于请求的工作流程中来使用。
大多数情况下,请求的工作流在不考虑请求优先级的情况下,也可以正常运行。然而,就像任何服务器一样,有时我们会遇到这样的情况:我们的两个后端中有一个遇到了麻烦,或者Zuul本身也遇到了麻烦。当这种情况发生时,具有更高优先级的请求将得到优先处理。优先级较高的请求将得到服务,而优先级较低的请求不会被接待。它的执行,类似于具有动态优先级阈值的优先级队列,允许Zuul丢弃优先级低于当前阈值的请求。
寻找最佳的流量控制点
在请求的整个生命周期中,Zuul有两个时间点可以执行负载分流:当它将请求,路由到特定的后端服务器时(服务器节流),或者在初始请求处理时,这会影响所有后端服务(全局节流)。
服务器节流
通过监控错误率和对后端服务的并发请求,Zuul可以感知该服务器何时出现问题。这两个指标,是故障和延迟的大致指标,当超过这两个指标阈值的百分之一时,我们将通过限制流量,来降低服务器负载。
全局节流
另一种情况是Zuul本身出现问题。与上面情况相反,全局节流将影响Zuul后面所有后端服务器,而不是单个后端服务器,这种全局节流的影响,可能会给成员国带来更大的问题。用于触发全局限制的关键指标,是CPU利用率、并发请求和连接数量,当超出任何一个这些指标的阈值时,Zuul将积极地限制流量,以在系统恢复的同时,保持自身的正常运行。这一功能非常关键:如果Zuul出现故障,流量就不能传送到我们的后端服务器,从而导致供应的全面中断。
引入基于优先级的渐进式负载分流
一旦确定了优先级,我们就可以将其与我们的减负机制结合起来,从而极大地提高流媒体的可靠性。当我们处于困境时(即超过上述任何阈值)时,我们会从最低优先级开始,逐步丢弃流量。三次函数用于管理节流级别,如果情况变得非常非常糟糕,这一水平将触及曲线的尖端,使其能够节掉任何流量。
上图是如何应用立方函数的一个示例。随着过载百分比的增加(即节流阈值和最大容量之间的范围),优先级阈值非常缓慢地落后于它:过载百分比35%,优先级阈值仍然在90左右。如果系统继续降级,我们的过载百分比会在超过80%的情况下,优先级达到50,然后在95%的情况下最终达到10,以此类推。
鉴于相对较少的请求,会影响流媒体可用性,限制低优先级流量,可能会影响某些产品功能,但不会阻碍到人们点击“播放”,观看他们最爱的节目。通过添加渐进式的,基于优先级的负载分流,Zuul可以减少足够的流量来稳定服务,而不会引起人们察觉。
应对重试风暴
当Zuul决定减少流量时,它会向设备发送信号,设备接收到信号会执行后退命令。Zuul通过指示设备可以执行的重试次数,以及可以在哪种时间窗口内执行这些重试命令,解决出现的问题。例如:
利用这种反压机制,我们可以比过去更快地停止重试风暴。我们根据请求的优先级自动调整这两个刻度盘,相比较低优先级的请求,具有较高优先级的请求会更积极地重试,这也增加了流媒体的可用性。
验证哪些请求适合作业
为了验证我们关于特定请求是否属于非关键型、体验降级型或关键型的分类假设,我们需要一种方法来测试,当用户在该请求被阻止时的体验。为此,我们利用了内部故障注入工具(FIT),并在Zuul中创建了一个故障注入点,允许我们根据提供的优先级,舍弃任何请求。这使我们能够通过阻止特定设备,或成员的优先级范围,来手动模拟减负体验,让我们了解哪些请求可以安全地减负,而不会影响用户。
持续确保这些请求仍然适用作业
这里的一个目标,是通过丢弃那些预计不会影响用户流媒体体验的请求,来减轻相对较糟的用户体验。然而,Netflix变化很快,原本被认为不关键的请求,可能会出人意料地变得关键。此外,Netflix拥有各种各样的客户端设备、客户端版本,以及与系统交互的方式,为了确保在这些场景中,限制非关键请求时不会给成员带来麻烦,我们利用了我们的基础设施实验平台CHAP。
该平台允许我们进行A/B实验,将少量产品用户,分配给对照组或实验组,45分钟的时间,同时限制实验组一系列的优先级。这使我们能够捕获各种实时使用案例,并衡量它们对回放体验的影响。CHAP分析每个设备成员的KPI,以确定对照组和实验组之间是否存在偏差。
在我们的第一个实验中,我们在Android和iOS设备上都检测到了低优先级请求的竞争情况,这会导致零星的播放错误。由于我们进行的是连续实验,所以一旦初始实验运行、并修复了错误,我们就安排它们继续定期运行。这使得当数据再次出现异常时,我们能够及早发现它,从而保持用户流媒体的正常运作。
FIX前后的实验数据回归检测
初尝成果
2019年,在渐进式分流法到位前,Netflix流媒体服务经历了一次中断,导致相当大比例的成员,在一段时间内无法看视频。2020年,在该方法实施部署几天后,团队开始看到这种解决方案的好处。Netflix也遇到过与2019年的服务中断事件类似、并有着相同潜在影响的事件。与当时不同的是,Zuul的渐进式减负手段开始发挥作用,开始减流流量,直到服务处于健康状态,而且完全不会影响视频播放的功能。
下图显示了稳定的每秒流可用性指标流(SPS),而Zuul正在根据事件期间请求的优先级进行渐进式负载分流。
图表中的不同颜色,表示被限制的优先级不同的请求。
就此,在基础架构从系统故障中自我恢复时,会员们仍能顺利地在Netflix上观看他们最喜欢的节目。