Download PDF version of this article PDF

大规模故障

快速变化下的可靠性


Ben Maurer,Facebook

故障是任何大规模系统工程的一部分。拥抱失败是 Facebook 的文化价值观之一。这可以从我们门洛帕克总部墙壁上张贴的海报中看出:“What Would You Do If You Weren't Afraid?” 和 “Fortune Favors the Bold。”

为了在快速变化的情况下保持 Facebook 的可靠性,我们研究了常见故障模式,并构建抽象概念来解决这些问题。这些抽象概念确保最佳实践应用于我们的整个基础设施。为了指导我们构建可靠性抽象概念的工作,我们必须了解我们的故障。我们通过构建工具来诊断问题,并创建一种审查事件的文化,这种文化促使我们做出改进,以防止未来发生故障。

为什么会发生故障?

虽然每个故障都有其独特的故事,但许多故障可以归结为少数几个根本原因。

单个机器故障

通常,单个机器会遇到孤立的故障,而不会影响基础设施的其余部分。例如,可能是机器的硬盘驱动器发生故障,或者特定机器上的服务遇到了代码中的错误,例如内存损坏或死锁。

避免单个机器故障的关键是自动化。自动化通过将已知的故障模式(例如,带有 S.M.A.R.T. 错误的硬盘驱动器)与搜索未知问题的症状(例如,通过更换响应时间异常缓慢的服务器)相结合,效果最佳。当自动化发现未知问题的症状时,手动调查可以帮助开发更好的工具来检测和修复未来的问题。

合理的负载变化

有时,Facebook 用户会以对我们的基础设施构成挑战的方式改变他们的行为。例如,在重大世界事件期间,独特类型的工作负载可能会以不寻常的方式给我们的基础设施带来压力。当贝拉克·奥巴马赢得 2008 年美国总统大选时,他的 Facebook 页面经历了创纪录的活动量。超级碗或世界杯等重大体育赛事中的高潮迭起的比赛会导致极高数量的帖子。“暗启动”(功能已激活但用户不可见)等负载测试有助于确保新功能能够处理负载。

在此类事件期间收集的统计数据通常为系统的设计提供了独特的视角。通常,重大事件会导致用户行为发生变化(例如,通过围绕特定对象创建集中的活动)。关于这些变化的数据通常指向设计决策,这些决策将允许在后续事件中更平稳地运行。

人为错误

鉴于 Facebook 鼓励工程师“快速行动,突破陈规”——办公室里张贴的另一张海报——人们可能会认为许多错误是由人为错误引起的。我们的数据表明,人为错误是我们故障的一个因素。图 1 包含对严重到足以被视为 SLA(服务级别协议)违规事件的时间分析数据。每次违规都表明我们的内部可靠性目标未得到满足并导致生成警报的情况。由于我们的目标非常严格,因此大多数此类事件都是轻微的,并且网站用户不会注意到。图 1a 显示,即使网站的流量在整个星期内保持一致,但周六和周日发生的事件明显减少。图 1b 显示了六个月期间,只有两周没有事件:圣诞节当周和员工预计要互相撰写同行评议的当周。

Fail at Scale: SLA violation events

这两个数据点似乎表明,当 Facebook 员工没有积极地对基础设施进行更改,因为他们忙于其他事情(周末、节假日,甚至绩效评估)时,网站的可靠性会更高。我们认为这并非是进行更改的人粗心大意造成的,而是表明我们的基础设施在面对非人为错误原因(例如机器故障)时,在很大程度上是自我修复的。

导致事件发生的三个简单方法

虽然故障有不同的根本原因,但我们发现了三种常见的病态,它们会放大故障并导致故障蔓延。对于每种病态,我们都开发了预防措施,以减轻广泛故障的影响。

快速部署的配置更改

配置系统往往被设计为在全球范围内快速复制更改。快速配置更改是一个强大的工具,可以让工程师快速管理新产品的发布或调整设置。但是,当部署错误的配置时,快速配置更改意味着快速失败。我们使用多种实践来防止配置更改导致故障。

让每个人都使用通用的配置系统。

使用通用的配置系统可确保程序和工具适用于所有类型的配置。在 Facebook,我们发现团队有时会倾向于以一次性的方式处理配置。避免这些诱惑并以统一的方式管理配置,使配置系统成为提高网站可靠性的杠杆方式。

静态验证配置更改。许多配置系统允许松散类型的配置,例如 JSON 结构。这些类型的配置使工程师很容易错误地键入字段名称、在需要整数的地方使用字符串或犯其他简单错误。最好使用静态验证来捕获这些直接的错误。结构化格式(例如,在 Facebook,我们使用 Thrift)4 可以提供最基本的验证。但是,编写程序化验证来验证更详细的要求并非不合理。

运行 Canary 测试。首先将您的配置部署到小范围的服务可以防止更改造成灾难性后果。Canary 测试可以采用多种形式。最明显的是 A/B 测试,例如仅向 1% 的用户发布新配置。可以同时运行多个 A/B 测试,并且您可以随时间使用数据来跟踪指标。

但是,出于可靠性目的,A/B 测试并不能满足我们的所有需求。部署到少量用户但导致相关服务器崩溃或内存不足的更改,显然会产生超出测试中有限用户的范围的影响。A/B 测试也很耗时。工程师通常希望在不使用 A/B 测试的情况下推送细微的更改。因此,Facebook 基础设施会自动在一小部分服务器上测试新配置。例如,如果我们希望向 1% 的用户部署新的 A/B 测试,我们将首先将测试部署到访问少量服务器的 1% 的用户。我们会监控这些服务器一小段时间,以确保它们不会崩溃或出现其他非常明显的问题。此机制为所有更改提供基本的“健全性检查”,以确保它们不会导致广泛的故障。

保留良好的配置。Facebook 的配置系统旨在在更新配置时,在面对故障时保留良好的配置。开发人员自然倾向于创建在收到无效的更新配置时会崩溃的配置系统。我们更喜欢在这些情况下保留旧配置并向系统操作员发出配置更新失败警报的系统。在旧配置下运行通常比向用户返回错误更可取。

使其易于回滚。有时,尽管尽了一切努力,但仍会部署错误的配置。快速找到并回滚更改是解决此类问题的关键。我们的配置系统由版本控制支持,使其易于回滚更改。

对核心服务的硬依赖

开发人员倾向于假设核心服务(例如配置管理、服务发现或存储系统)永远不会失败。但是,即使这些核心服务中的短暂故障也可能演变成大规模事件。

缓存来自核心服务的数据。通常没有必要对这些类型的服务进行硬依赖。这些服务返回的数据可以缓存,以便大多数服务在这些系统之一短暂中断期间继续运行。

提供强化的 API 以使用核心服务。最好通过通用库来补充核心服务,这些通用库在使用这些核心服务时遵循最佳实践。例如,库可以提供良好的 API 来管理缓存或良好的故障处理。

进行消防演习。您可能认为您能够承受核心服务的停机,但在尝试之前您永远不会知道。对于这些类型的停机,我们不得不开发用于消防演习的系统,范围从应用于单个服务器的故障注入到手动触发整个数据中心的停机。

延迟增加和资源耗尽

某些故障会导致服务对客户端的延迟增加。延迟的增加可能很小(例如,考虑人为配置错误导致 CPU 使用率增加,但仍在服务容量范围内),或者可能接近无限大(服务中服务响应的线程已死锁)。虽然少量的额外延迟可以很容易地被 Facebook 的基础设施处理,但大量的延迟会导致级联故障。几乎所有服务都对未完成请求的数量有限制。此限制可能是由于每个请求服务中的线程数量有限,或者可能是由于基于事件的服务中的内存有限。如果服务遇到大量额外延迟,则调用它的服务将耗尽其资源。此故障可以通过许多服务层传播,从而导致广泛的故障。

资源耗尽是一种特别具有破坏性的故障模式,因为它允许仅由一部分请求使用的服务的故障导致所有请求的故障。例如,假设一个服务调用一个新的实验性服务,该服务仅向 1% 的用户启动。通常,对该实验性服务的请求需要 1 毫秒,但由于新服务中的故障,请求需要 1 秒。针对使用此新服务的 1% 用户的请求可能会消耗大量线程,以至于其他 99% 用户的请求无法运行。

我们发现了一些可以避免这种累积的技术,并且误报率很低。

受控延迟。在分析过去涉及延迟的事件时,我们发现许多最严重的事件都涉及大量请求滞留在队列中等待处理。相关服务具有资源限制(例如活动线程数或内存),并将缓冲请求以使使用率保持在限制以下。由于服务无法跟上传入请求的速度,队列会越来越大,直到达到应用程序定义的限制。为了解决这种情况,我们希望限制队列的大小,而又不影响正常运行期间的可靠性。我们研究了关于缓冲膨胀的研究,因为我们的问题似乎很相似——需要在排队以确保可靠性的同时,避免在拥塞期间造成过多的延迟。我们尝试了 CoDel1(受控延迟)算法的变体

onNewRequest(req, queue)

  if (queue.lastEmptyTime() < (now - N seconds)) {
     timeout = M ms
  } else {
     timeout = N seconds;
  }
  queue.enqueue(req, timeout)

在此算法中,如果队列在过去的 N 毫秒内未空,则队列中花费的时间限制为 M 毫秒。如果服务能够在过去的 N 毫秒内清空队列,则队列中花费的时间限制为 N 毫秒。此算法可防止常设队列(因为 lastEmptyTime 将在遥远的过去,从而导致 M 毫秒的排队超时),同时允许短时间的排队以确保可靠性。虽然具有如此短超时的请求似乎违反直觉,但此过程允许在系统无法跟上传入请求的速度时快速丢弃请求,而不是构建请求。短超时可确保服务器始终只接受比实际处理能力稍多的工作,因此永远不会空闲。

此算法的一个吸引人的特性是 MN 的值往往不需要调整。其他解决常设队列问题的方法(例如,设置队列中项目数量的限制或设置队列的超时时间)需要在每个服务的基础上进行调整。我们发现 M 值为 5 毫秒,N 值为 100 毫秒往往在各种用例中都能很好地工作。Facebook 的开源 Wangle 库5 提供了此算法的实现,我们的 Thrift4 框架使用了该实现。

自适应 LIFO(后进先出)。大多数服务以 FIFO(先进先出)顺序处理队列。但是,在排队时间较长的情况下,先进的请求通常已经等待了很长时间,以至于用户可能已经中止了生成该请求的操作。与刚刚到达的请求相比,首先处理先进的请求会将资源花费在不太可能使用户受益的请求上。我们的服务使用自适应 LIFO 处理请求。在正常运行条件下,请求以 FIFO 顺序处理,但当队列开始形成时,服务器会切换到 LIFO 模式。自适应 LIFO 和 CoDel 可以很好地协同工作,如图 2 所示。CoDel 设置了短超时,防止长队列的形成,而自适应 LIFO 将新请求放在队列的前面,最大限度地提高了它们满足 CoDel 设置的截止日期的机会。HHVM3(Facebook 的 PHP 运行时)包含自适应 LIFO 算法的实现。

Fail at Scale: LIFO (left) and adaptive LIFO with CoDel (right)

并发控制。CoDel 和自适应 LIFO 都在服务器端运行。服务器通常是实施延迟预防措施的最佳位置——服务器往往为大量客户端提供服务,并且通常比其客户端拥有更多信息。但是,某些故障非常严重,以至于服务器端控件无法启动。因此,我们在客户端实施了权宜之计。每个客户端都会跟踪每个服务的未完成出站请求的数量。当发送新请求时,如果对该服务的未完成请求数超过可配置的数字,则该请求会立即标记为错误。此机制可防止单个服务垄断其所有客户端的资源。

有助于诊断故障的工具

尽管采取了最佳的预防措施,但一些故障总是会发生。在中断期间,正确的工具可以快速找到根本原因,从而最大限度地缩短故障持续时间。

使用 Cubism 的高密度仪表板

在处理事件时,快速访问信息非常重要。良好的仪表板使工程师能够快速评估可能异常的指标类型,然后使用此信息来假设根本原因。但是,我们发现我们的仪表板变得如此庞大,以至于难以快速导航它们,并且这些仪表板上显示的图表包含太多线条,难以一目了然地阅读,如图 3 所示。

Fail at Scale: Difficult-to-read charts from a typical dashboard

为了解决这个问题,我们使用 Cubism2(一个用于创建水平线图的框架)构建了我们的顶级仪表板——水平线图是使用颜色更密集地编码信息的折线图,可以轻松比较多个相似的数据序列。例如,我们使用 Cubism 来比较不同数据中心之间的指标。我们围绕 Cubism 的工具允许轻松的键盘导航,因此工程师可以快速查看多个指标。图 4 显示了使用面积图和水平线图在不同高度下的相同数据集。在面积图版本中,30 像素版本难以阅读。另一方面,即使在 30 像素的高度下,水平线图也可以非常容易地找到峰值。

Fail at Scale: Area charts (left) and horizon charts (right)

刚刚发生了什么变化?

由于人为错误是导致故障的首要原因之一,因此调试故障最有效的方法之一是查找人类最近更改的内容。我们收集有关最近更改的信息,范围从配置更改到 OpsStream 工具中新软件的部署。但是,随着时间的推移,我们发现此数据源变得非常嘈杂。由于成千上万的工程师进行更改,因此在事件期间通常有太多更改需要评估。

为了解决这个问题,我们的工具尝试将故障与相关更改关联起来。例如,当抛出异常时,除了输出堆栈跟踪之外,我们还会输出该请求读取的任何最近更改过值的配置设置。通常,生成许多堆栈跟踪的问题的原因是这些配置值之一。然后,我们可以快速响应问题——例如,通过回滚配置并让进行更改的工程师参与进来。

从故障中学习

故障发生后,我们的事件审查流程有助于我们从这些事件中学习。

事件审查流程的目标不是追究责任。没有人因为他或她造成的事件受到审查而被解雇。审查的目标是了解发生了什么、补救导致事件发生的情况,并建立安全机制以减少未来事件的影响。

事件审查方法

Facebook 开发了一种称为 DERP(检测、升级、补救和预防的首字母缩写)的方法,以帮助进行富有成效的事件审查。

检测。问题是如何检测到的——警报、仪表板、用户报告?

升级。合适的人员是否快速参与进来?这些人是否可以通过警报而不是手动方式引入?

补救。采取了哪些步骤来解决问题?这些步骤可以自动化吗?

预防。可以进行哪些改进来消除再次发生此类故障的风险?您如何才能优雅地失败,或者更快地失败以减少此故障的影响?

DERP 有助于分析手头事件的每个步骤。借助此分析,即使您无法阻止此类事件再次发生,您至少也能够在下次更快地恢复。

通过减少故障更快地行动

“快速行动”的心态不必与可靠性相悖。为了使这些理念兼容,

Facebook 的基础设施提供了安全阀:我们的配置系统可防止快速部署错误的配置;我们的核心服务为客户端提供强化的 API,以防止故障;我们的核心库可防止在面对延迟时资源耗尽。为了处理不可避免的漏洞,我们构建了易于使用的仪表板和工具,以帮助查找可能导致正在调查的问题的最近更改。最重要的是,在事件发生后,我们利用吸取的教训来提高基础设施的可靠性。

参考文献

1. CoDel(受控延迟)算法; https://queue.org.cn/detail.cfm?id=2209336

2. Cubism; https://square.github.io/cubism/

3. HipHop 虚拟机 (HHVM); https://github.com/facebook/hhvm/blob/43c20856239cedf842b2560fd768038f52b501db/hphp/util/job-queue.h#L75

4. Thrift 框架; https://github.com/facebook/fbthrift

5. Wangle 库; https://github.com/facebook/wangle/blob/master/wangle/concurrent/Codel.cpp

Ben Maurer 是 Facebook Web Foundation 团队的技术主管,该团队负责 Facebook 面向用户的产品的整体性能和可靠性。Ben 于 2010 年加入 Facebook,成为基础设施团队的成员。在 Facebook 之前,他与 Luis von Ahn 共同创立了 reCAPTCHA。最近,Ben 与美国数字服务部合作,以改进联邦政府内部的技术使用。

© 2015 1542-7730/15/0500 $10.00

acmqueue

最初发表于 Queue vol. 13, no. 8
数字图书馆 中评论本文





更多相关文章

Niklas Blum, Serge Lachapelle, Harald Alvestrand - WebRTC - 开放 Web 平台的实时通信
在这个疫情时期,世界比以往任何时候都更加依赖于基于互联网的 RTC(实时通信)。在过去十年中,RTC 产品的数量呈爆炸式增长,这在很大程度上是由于更便宜的高速网络接入和更强大的设备,但也归功于一个名为 WebRTC 的开放、免版税平台。WebRTC 正在从支持有用的体验发展为在疫情期间让数十亿人继续工作和教育,并保持重要的人际接触方面发挥着至关重要的作用。WebRTC 未来的机遇和影响确实令人着迷。


Benjamin Treynor Sloss, Shylaja Nukala, Vivek Rau - 重要的指标
衡量您的网站可靠性指标,设定正确的目标,并完成准确衡量指标的工作。然后,您会发现您的服务运行得更好,中断更少,用户采用率更高。


Silvia Esparrachiari, Tanya Reilly, Ashleigh Rentz - 跟踪和控制微服务依赖项
如果您曾经将钥匙锁在房屋或汽车内,您就会熟悉依赖循环。您无法在没有钥匙的情况下打开锁,但您无法在不打开锁的情况下获得钥匙。有些循环是显而易见的,但更复杂的依赖循环在导致中断之前可能难以找到。跟踪和控制依赖项的策略对于维护可靠的系统是必要的。


Diptanu Gon Choudhury, Timothy Perrett - 为互联网规模的服务设计集群调度器
希望构建调度系统的工程师应考虑他们使用的底层基础设施的所有故障模式,并考虑调度系统的运营商如何在租户系统所有者进行故障排除期间配置补救策略,同时帮助保持租户系统尽可能稳定。





© 保留所有权利。

© . All rights reserved.