在冬季2012年,Netflix公司遭受了 长时间断电 持续了七个小时,由于在美东地区的AWS弹性负载均衡服务的问题。(Netflix的运行在Amazon Web Services的[AWS] -我们没有自己的任何数据中心,所有与Netflix的互动是从AWS服务,除了视频的实际流。一旦你点击“播放”时,实际的视频文件是从我们自己的CDN服务。)在停电,没有流量进入美东是达到了我们的服务。
为了防止这种情况再次发生,我们决定建立地区性故障切换的系统,该系统是有弹性的,以我们的基础服务提供商的故障。故障转移是保护计算机系统从故障时主系统出现故障时,其中备用设备自动接管的方法。
区域故障转移降低了风险
我们扩展到三个AWS区域:两个在美国(美国东部和美国西部),一个在欧盟(EU)。我们保留了足够的容量来执行故障转移,以便我们可以吸收单个区域的中断。
典型的故障转移如下所示:
-
1.意识到其中一个地区遇到了麻烦。
-
2.扩大两个救世主地区。
-
3.代理从陷入困境的区域到救世主的一些流量。
-
4.将DNS从问题区域更改为救世区域。
接下来让我们探索每一步。
1.找出问题
我们需要指标,最好是一个指标,它可以告诉我们系统的健康状况。在Netflix,我们使用称为每秒流启动的业务指标(简称SPS)。这是已成功开始播放节目的客户端数量。
我们按区域划分了这些数据,并且在任何给定时间我们都可以绘制每个区域的SPS数据,并将其与前一天和前一周的SPS值进行比较。当我们注意到SPS图表下降时,我们知道我们的客户无法启动流媒体节目,因此我们遇到了麻烦。
此问题不一定是云基础设施的问题。这可能是组成 Netflix 生态系统的数百个微服务中的一个糟糕代码的部署,例如海底电缆的切割等。我们可能不知道其原因;我们只知道某处出了问题。
如果仅在一个区域中观察到 SPS 中的这种下降,则它是区域性故障转移的理想选择。如果在多个区域观察到下降,很不幸,因为我们只有足够的能力一次疏解一个区域。这正是我们将微服务一次部署到一个区域的原因。如果部署出现问题,我们可以立即撤销并稍后调试此问题。同样,我们希望避免故障转移出现在流量重定向之后(如在 DDoS 攻击中发生的那样)。
2.扩大救世主
一旦我们确定了病区,我们就应该准备其他区域(“救世主”)来接收来自病区的交通。在我们打开消防水带之前,我们需要适当地缩放救生区域中的堆叠。
在这种情况下,缩放的适当含义是什么?Netflix的流量模式全天都不是静态的。我们有高峰观看时间,通常是在下午6点到9点之间。但是在世界不同地区的不同时间都有6次拍摄。美国东部的交通高峰时段比美国西部高3小时,比欧盟地区落后8小时。
当我们在美国东部进行故障转移时,我们会将来自美国东部的流量发送到欧盟,并将流量从南美洲发送到美国西部。这是为了降低延迟并为我们的客户提供最佳体验。
考虑到这一点,我们可以使用线性回归来预测将使用每个微服务的历史扩展行为路由到一天中的特定时间(以及每周的一天)的救急区域的流量。
一旦我们确定了每个微服务的适当大小,我们通过设置每个集群的所需大小来触发每个微服务的扩展,然后让 AWS 发挥其魔力。
3. 代理流量
既然微服务集群已经扩展,我们开始代理从故障区域到救急区域的流量。Netflix 已经构建了一个名为 Zuul 的高性能、跨区域边缘代理,并已经对其开源。
这些代理服务旨在验证请求、执行减载、重试已失败请求等。Zuul 代理也可以执行跨区域代理。我们使用此功能将一小撮流量路由到远离故障区域,然后逐渐增加已重新路由的流量,直到达到 100%。
这种渐进式代理允许我们的服务使用其扩展策略来执行任意用于处理接入流量所需的响应式扩展。这是为了补偿我们在进行扩展预测所耗费的时间与扩展每个集群所耗费时间之间的流量变化。
Zuul 在这一点上完成了繁重的工作,它将所有来自故障区域的流量路由到正常区域。但现在是完全废弃受影响区域的时候了。此处是 DNS 切换发挥作用的地方。
4. 切换 DNS
故障转移的最后一步是更新指向受影响区域的 DNS 记录,并将它们重定向到正常区域。这将完全把所有客户流量移出故障区域。任何未使其 DNS 缓存失效的客户端仍将由受影响区域中的 Zuul 层进行路由。
这是故障转移通常在 Netflix 中执行的背景信息。这个过程需要很长时间才能完成 —— 大约 45 分钟(在状况良好的情况下)。
使用全新进程加速响应处理
我们注意到大部分时间(大约35分钟)都在等待应急区域扩展中。即使 AWS 可以在几分钟内为我们配置新实例、启动服务、进行及时预热,以及在 discovery 中注册 UP 主导扩展进程之前处理其他启动任务。
我们认为这耗时太长了。我们希望我们的故障转移在 10 分钟内完成。我们希望在不增加服务所有者的运营负担的情况下这样做。我们还希望保证开销不变。
我们在这三个区域保留容量以吸收故障转移流量;试想如果我们已经为所有的容量买单,为什么不使用它呢?于是启动了 Nimble 项目。
我们的想法是为每个微服务维护热备份中的实例池。当我们准备进行故障转移时,我们可以简单地将热备份注入集群,以接收实时通信。
未使用的预留容量称为槽(trough)。Netflix 的一些团队使用一些槽容量来运行批处理作业,因此我们不能简单地将所有可用槽转换为热备用。相反,我们可以为我们运行的每个微服务维护一个影子集群,并将该影子集群存储在足够的实例中,以获取当天的故障转移流量。其余的实例可供批处理作业随意使用。
在故障转移时,我们将实例从影子集群注入到实时集群中,而不是使用触发 AWS 为我们配置实例的传统扩展方法。这个过程大约需要四分钟,而不是过去的 35 分钟。
由于我们的容量注入很快,我们不必通过代理谨慎地移动流量以等待扩展策略做出响应。我们可以轻松地切换 DNS 并打开开关,从而在中断期间减少所耗费的更多宝贵时间。
我们在阴影集群中添加了过滤器,以防止故障实例上报指标。否则,它们将污染度量标准空间并混淆正常的操作行为。
我们还通过修改我们的 discovery 客户端来阻止影子集群中的实例在 discovery 中注册 UP。在我们触发故障转移之前,这些实例将继续保持在不可用状态中。
现在我们可以在七分钟内完成区域故障转移。 由于我们利用现有的预留容量,因此我们不会产生任何额外的基础设施开销。编排故障转移的软件是由三名工程师组成的团队用 Python 编写的。
关于作者
Amjith Ramanujam —— Amjith Ramanujam是Netflix的高级软件工程师。他的团队负责Netflix服务在极端逆境时保持运行。换句话说,他的团队负责区域故障转移。在他的业余时间里写现代的CLI工具。他是pgcli和mycli的创始人。您可以在twitter上向他打招呼。