Netflix按优先级减载保障可用性(二)
July 3, 2024
背景
已经在之前的文章中介绍了Netflix在API网关Zuul中实施的按优先级减载策略来保障服务的可用性。当前这一策略已经扩展到了单个服务级别。重点关注在视频控制层面(即AWS中的后台服务)和数据平面。
相对于之前在API网关Zuul中的实现,在服务级别中使用优先减载策略的优势在于:
- 服务团队可以拥有自己的优先级逻辑,并可以应用更细粒度的优先级。
- 可用于后端到后端的通信,即不位于我们的边缘 API 网关后面的服务。
- 服务可以通过将不同类型的请求组合到一个集群中,并在必要时删除低优先级的请求,而不是维护单独的集群来进行故障隔离,从而更有效地利用云容量。
方案引入
总体思路是先在PlayAPI服务中引入优先减载策略,验证后再全面推广。PlayAPI是视频流控制平面上的一个关键后端服务,用于播放前的manifest拉取。
根据请求的重要性将其分为两类:
- 用户发起的请求(关键):
这些请求是在用户点击播放时发出的,直接影响用户开始观看节目或电影的能力。
- 预取请求(非关键):
为减少终端等待耗时所做的预加载类请求,失败也不会影响播放,仅增加少量的加载耗时。
问题
PlayAPI之前采用并发控制器来限流,但是没有区分流量类型,这会导致两类流量之间的相互影响。
可行的解决方案之一是隔离两类流量,将关键请求和非关键请求分片到单独的集群。但是带来的问题是成本的增加,包括开发(系统复杂度)、维护(机器、流水线、监控)成本。
解决方案
不进行物理隔离,而是在 PlayAPI 中实现了一个并发限制器,限制器优先处理用户发起的请求,而非预取请求。
此机制使用开源Netflix/concurrency-limits Java 库的分区功能。我们在限制器中创建了两个分区:
- 用户启动的分区(User-Initiated Partition):
保证 100% 的吞吐量。
- 预取分区(Pre-fetch Partition):
仅利用多余的容量。
每个实例都可处理两种类型的请求,并且具有两个分区,分区的大小可动态调整,以确保预取请求仅获得过剩容量。这允许用户发起的请求在必要时“窃取”预取容量。
分区限制器配置为预处理Servlet 过滤器,它使用设备发送的 HTTP 标头来确定请求的关键性,从而避免了读取和解析被拒绝请求的请求主体的需要。这确保了限制器本身不是瓶颈,并且可以在使用最少 CPU 的情况下有效地拒绝请求。例如,过滤器可以初始化为
请注意,在稳定状态下,不会出现任何限制,优先级对预取请求的处理也没有影响。优先级机制仅在服务器达到并发限制并需要拒绝请求时才会启动。
可以将一个或多个 Servlet 过滤器附加到一个 Servlet 或一组 Servlet。Servlet 过滤器也可以附加到 JavaServer Pages (JSP) 文件和 HTML 页面。调用 Servlet 前调用所有附加的 Servlet 过滤器。
Servlet 过滤器是可用于 Servlet 编程的 Java 类,可以实现以下目的:
- 在客户端的请求访问后端资源之前,拦截这些请求。
- 在服务器的响应发送回客户端之前,处理这些响应。
实际应用效果
在部署优先负载削减几个月后,Netflix 的基础设施出现一次故障,影响了许多用户的流媒体播放。故障修复后,Android 设备每秒的预取请求量激增 12 倍,可能是因为排队请求积压。
虽然预取请求的可用性下降到 20%,但由于优先减载,用户发起请求的可用性达到 99.4% 以上。
预取和用户发起请求的可用性
我们一度限制了超过 50% 的所有请求,但用户发起的请求的可用性仍然保持在 99.4% 以上。
通用策略
基于这种方法的成功,我们创建了一个内部库,使服务能够根据可插入利用率措施执行优先负载削减,具有多个优先级别。
与需要处理大量具有不同优先级的请求的 API 网关不同,大多数微服务通常只接收具有几个不同优先级的请求。为了保持不同服务之间的一致性,我们引入了四个预定义的优先级存储桶,灵感来自Linux tc-prio 级别:
- CRITICAL:影响核心功能 — 如果我们没有彻底失败,这些功能就永远不会消失。比如播放鉴权。
- DEGRADED:影响用户体验 — 随着负载的增加,这些功能将逐渐消失。比如推荐。
- BEST_EFFORT:不会影响用户 — 我们将尽最大努力来响应这些影响,并且可能会在正常运行中逐步消除。比如观看历史同步。
- BULK:APP的后台类工作,比如异步数据上报。
服务可以选择上游客户端的优先级,也可以通过检查各种请求属性(例如 HTTP 标头或请求正文)将传入请求映射到这些优先级存储桶之一,以实现更精确的控制。以下是服务如何将请求映射到优先级存储桶的示例:
基于通用 CPU 的负载削减
优先级减载仅在达到目标 CPU 利用率后才开始减载,并且随着系统负载的增加,将逐步减载更关键的流量以保持用户体验。
例如,如果集群的自动扩展目标是 CPU 利用率达到 60%,则可以将其配置为在 CPU 利用率超过此阈值时开始减少请求。当流量高峰导致集群的 CPU 利用率大大超过此阈值时,它将逐渐减少低优先级流量以节省资源用于高优先级流量。这种方法还为自动扩展(即服务扩容)提供了更多时间,以便向集群添加更多实例。一旦添加了更多实例,CPU 利用率就会降低,低优先级流量将恢复正常服务。
根据不同优先级存储桶的 CPU 利用率(X 轴)计算的负载削减请求百分比(Y 轴)
优先级减载策略的目标:
- 按流量优先级减载
- 进入减载模式之后,系统CRITICAL流量的成功率以及耗时不应该有明显的变化。
原文中还有相关实验来验证减载策略的有效性。
基于通用 I/O 的负载削减
有些服务不是 CPU 密集型的,而是 IO 密集型的,受支持服务或数据存储的影响,当它们在计算或存储容量方面超载时,它们可以通过增加延迟来施加反压。对于这些服务,我们重新使用优先负载削减技术,但我们引入了新的利用率指标来输入削减逻辑。除了标准自适应并发限制器(本身是平均延迟的度量)之外,我们的初始实现还支持两种基于延迟的削减形式:
- 该服务可以指定每个端点的目标和最大延迟,当服务异常缓慢时,无论后端如何,这都允许服务脱落。
- 数据网关上运行的 Netflix 存储服务返回观察到的存储目标和最大延迟 SLO 利用率,从而允许服务在分配的存储容量超载时进行卸载。
结语
容灾策略的整体思路应该是越接近故障点实施容灾策略效果越好,所以Netflix在之前API网关实施减载策略的基础上进一步推广至微服务级。但是实施的思路是相同的:
- 做好流量分类,区分核心流量和非核心流量。可以根据业务场景做更加精细化的拆分。精准的流量分类是一切的前提。
- 仅在当系统负载到阈值时启用按优先级降载策略,按当前负载+优先级策略,丢弃(或者降级)低优先级流量,保障高优先级流量的服务可用性。
- 优先级确认:终端携带表示或者主动根据上游识别。
- 有效验证:流量模拟压测,验证各优先级流量的成功率和耗时。理论上高优先级流量的成功率、耗时影响不大。