当前位置: 首页 > 科技观察

FacebookHowAvoidsLarge-ScaleOnlineFailure

时间:2023-03-18 20:25:13 科技观察

FailatScale是Facebook2015年发表在acmqueue上的一篇文章,主要写常见的线上故障和解决方法,内容比较现实。“如果你不害怕,你会做什么?”和“命运眷顾勇敢者”。是FB公司的信条,挂在墙上的那种。为了在快速变化的系统中稳定FB的系统,工程师们对系统故障做了一些总结和抽象。为了能够构建可靠的系统,有必要了解故障。为了解故障,工程师构建工具来诊断问题并营造回顾文化以避免未来再次发生。事故的发生可分为三类。为什么会发生故障单台机器故障通常,单台机器遇到的孤立故障不会影响基础架构的其余部分。这里的单机故障是指:某台机器的硬盘出现故障,或者某台机器上的服务遇到代码错误,比如内存损坏或者死锁。避免个别机器故障的关键是自动化。通过总结已知的故障模式并将它们与探索未知的故障症状相结合。当发现未知故障症状(如响应慢)时,将机器拆除,离线分析,归纳为已知故障。WorkloadchangesMajorreal-worldeventscanstressFacebook'ssiteinfrastructure,suchas:BarackObamawaselectedpresidentandhisFacebookpageexperiencedrecordlevelsofactivity.超级碗或世界杯等重大体育赛事的高潮可能会导致极高的帖子数。负载测试,以及新功能发布时的引流阶段,不会向用户展示,但会吸引流量。在这些事件中收集的统计数据为系统设计提供了独特的视角。重大事件会导致用户行为发生变化,这些变化可以为系统后续的决策提供数据依据。人为错误图1a显示了周六和周日的事故是如何大幅减少的,尽管该站点的流量在整个星期内保持稳定。图1b显示,在六个月的时间里,只有两周没有发生事故:圣诞节那一周和员工需要为彼此撰写同行评论的那一周。从上图可以看出,大部分的故障都是人为造成的,因为事件的统计是符合人类活动规律的。失败的三种原因失败的原因有很多,但最常见的是三种。此处列出的每一项都给出了预防措施。快速部署的配置更改配置系统通常设计用于在全球范围内快速复制更改。快速配置更改是强大的工具,但是,快速配置更改可能会在部署有问题的配置时导致事故。以下是一些防止配置更改失败的方法。每个人都共享相同的配置系统。使用通用配置系统可确保程序和工具适用于所有类型的配置。配置更改的静态检查。许多配置系统允许松散类型的配置,例如JSON结构。这些类型的配置很容易让工程师输入错误的字段名称,在需要整数的地方使用字符串,或者犯其他简单的错误。像这样的简单错误最好用静态验证来捕获。结构化格式(Facebook使用Thrift)可以提供最基本的验证。但是,编写程序来执行更详细的配置业务级验证也是合适的。金丝雀部署。首先将配置部署到一个小区域,以防止灾难性的变化。金丝雀可以有多种形式。最简单的是A/B测试,例如只为1%的用户启用新配置文件。可以同时运行多个A/B测试,并且可以使用一段时间内的数据跟踪指标。谈到可靠性,A/B测试并不是一切。部署到少量用户的更改,如果导致相关服务器崩溃或内存不足,显然会超出测试中限制的用户数量。A/B测试也很耗时。工程师通常希望在不使用A/B测试的情况下推动小的变化。为了避免明显的配置问题,Facebook的基础设施会自动在一小组服务器上测试新版本的配置。比如我们要对1%的用户部署一个新的A/B测试,我们会先对1%的用户部署测试,保证这些用户的请求落在少数服务器上,监控这些服务器短时间,以确保不会因配置更新而导致非常明显的崩溃。坚持有效的配置。配置系统设计方案确保在发生故障时保留原始配置。当配置出现问题时,开发人员通常更愿意直接让系统崩溃,但Facebook的基础设施开发人员认为,使用旧配置使模块工作比向用户返回错误要好得多。(注:我只能说这个真的要看场景)配置有问题的时候回滚应该很快。有时,尽管尽了最大努力,但有问题的配置仍会上线。快速回滚是解决这类问题的关键。配置内容在版本管理系统中进行管理,保证可以回滚。对核心服务的强烈依赖开发人员倾向于认为核心服务:例如配置管理、服务发现或存储系统,永远不会失败。在这种假设下,这些核心服务的短暂故障可能会变成大规模故障。为核心服务缓存数据。部分数据可以缓存在服务本地,可以减少对缓存服务的依赖。提供专用SDK使用核心服务。最好为核心服务提供专用的SDK,这样大家在使用核心服务的时候就可以遵循相同的最佳实践。同时在SDK中可以考虑缓存管理和故障处理,让用户一劳永逸。进行演练。只要不演练,就不知道依赖服务挂了是不是真的会挂,所以通过演练进行故障注入是很有必要的。延迟增加和资源耗尽某些故障会导致延迟增加,延迟可能很小(例如,导致CPU使用率略有增加),也可能很大(使提供响应的线程死锁)。Facebook的基础架构可以轻松处理少量额外延迟,但大量延迟会导致级联故障。几乎所有服务都对未完成请求的数量有限制。这种限制可能是由于请求-响应服务的线程数量有限,或者基于事件的服务的内存有限。如果服务遇到大量额外延迟,调用它的服务将耗尽其资源。这种故障逐层传播,造成大故障。资源耗尽是一种特别具有破坏性的故障模式,它会导致请求子集使用的服务失败导致所有请求失败:服务调用新的实验性服务,该服务仅向1%的用户推出。正常情况下,对这个实验服务的请求需要1毫秒,但是由于新服务的失败,请求需要1秒。1%的用户使用这个新服务的请求可能会消耗很多线程,导致其他99%的用户的请求无法执行。以下手段可以避免请求堆积:延迟控制。在分析过去涉及延迟的事件时,工程师发现大量请求正在排队等待处理。服务通常对线程数或内存使用量有限制。由于服务响应速度<请求传入速度,队列将增长直到达到阈值。为了在不影响正常运行可靠性的情况下限制队列大小,FB工程师研究了bufferbloat问题。这里的问题很像bufferbloat问题,在拥塞时不会造成过大的延迟。这里实现了CoDel(controlleddelay的缩写)算法的一个变种:注意:虽然里面写了M和N,但是M和N其实是固定值,N=100ms,M=5msonNewRequest(req,queue):ifqueue.lastEmptyTime()<(now-Nseconds){timeout=Mms}else{timeout=Nseconds;}queue.enqueue(req,timeout)在这个算法中,如果队列在过去100ms内没有被清空,那么队列限制为5毫秒。如果服务能够在过去的100毫秒内清空队列,则在队列中花费的时间限制为100毫秒。该算法减少了排队(因为lastEmptyTime已经过去很久了,导致5毫秒的排队超时),同时为了可靠性目的允许短排队。虽然让请求具有如此短的超时时间似乎违反直觉,但这个过程允许请求被快速丢弃,而不是在系统跟不上传入请求的速度时堆积起来。较短的超时确保服务器总是接受比它实际处理的多一点的工作,所以它永远不会闲置。前面说了,这里的M和N基本不用根据场景调整。队列问题的其他解决方案,例如设置队列中项目数量的限制或设置队列的超时时间,需要根据场景进行调整。M的固定值5毫秒和N的固定值100毫秒在大多数情况下效果很好。Facebook的开源Wangle库和Thrift就使用了这个算法。自适应LIFO(后进先出)。大多数服务以FIFO顺序处理队列。但是在大排队期间,第一个进来的请求往往已经等待了很长时间,用户可能已经放弃了与请求相关的动作。此时,处理队列中的第一个请求将消耗资源在不太可能使用户受益的请求上。FB的服务使用自适应的后进先出算法来处理请求。在正常运行情况下,请求以先进先出模式处理,但当队列开始积累任务时,切换到后进先出模式。如图2所示,AdaptiveLIFO和CoDel可以很好地协同工作。CoDel设置了一个较短的超时时间,防止形成长队,而自适应LIFO模式将新请求放在队列的前面,最大限度地提高他们满足CoDel设定的截止日期的机会。HHVM实现了这种后进先出算法。并发控制。CoDel和AdaptiveLIFO都在服务器端运行。服务器是减少延迟的最佳场所——服务器为大量客户端提供服务,并且拥有比客户端更多的信息。但是有些故障比较严重,可能会导致服务器的控制无法启动。作为旁注,FB还在客户端实施了一项政策:每个客户端都会跟踪每个服务的未完成出站请求数。当发送新的请求时,如果服务的待处理请求数超过可配置的数量,则该请求将立即标记为错误(注意:应该类似于断路器)。这种机制可以防止单个服务独占其客户端的所有资源。帮助诊断故障的工具尽管采取了最好的预防措施,故障还是会发生。在故障期间,使用正确的工具可以快速找到根本原因并将故障持续时间降至最低。使用Cubism的高密度仪表板处理事件时,快速访问信息非常重要。好的仪表板可以让工程师快速评估可能异常的指标类型,然后使用此信息推测根本原因。然而,仪表板越来越大,直到变得难以快速扫描,并且这些仪表板上显示的图表有太多线条,无法一眼看懂,如图3所示。为了解决这个问题,我们构建了我们的顶层使用Cubism的仪表板,这是一种用于创建水平图表的框架——使用颜色更密集地编码信息的图表,允许轻松可视化多个相似的数据曲线。比较。例如,我们使用Cubism来比较不同数据中心的指标。我们围绕Cubism的工具支持轻松的键盘导航,使工程师能够快速查看多个指标。图4使用面积图和水平折线图显示了不同高度的同一数据集。在面积图版本中,30px的高版本很难阅读。在地平线图上很容易找到同一高度的山峰。最近发生了什么变化?由于故障的首要原因之一是人为错误,因此调试故障的最有效方法之一是寻找最近的人为更改。我们在名为OpsStream的工具中收集有关最近更改的信息,从配置更改到新版本部署。然而,我们发现这个数据源随着时间的推移变得非常嘈杂。由于有成千上万的工程师在进行更改,一次事件中的更改往往太多而无法评估。为了解决这个问题,我们的工具尝试将失败与其相关的更改联系起来。例如,当抛出异常时,除了输出堆栈跟踪外,我们还输出请求读取的任何配置值的最新变化。通常,生成许多错误堆栈的问题是由这些配置值之一引起的。然后我们可以快速对问题做出反应。比如让发起配置的工程师快速回滚配置。从失败中吸取教训发生失败后,我们的事件审查流程可帮助我们从这些事件中吸取教训。事件审查流程的目的不是指责。没有人会因为他或她造成的事件受到审查而被解雇。审查的目的是了解发生了什么,补救导致事件发生的情况,并建立安全机制以减少未来事件的影响。一种审查事件的方法Facebook发明了一种称为DERP(检测、升级、补救和预防)的方法来帮助进行富有成效的事件审查。检测检测。如何检测问题——警报、仪表板、用户报告?升级升级。合适的人是否迅速介入?这些人能否通过警报而不是人为地被拉入故障排除过程?补救措施。已采取哪些步骤来解决此问题?这些步骤可以自动化吗?预防预防。哪些改进可以防止相同类型的故障再次发生?您如何优雅地失败或更快地失败,以减少此失败的影响?在这种模式的帮助下,即使不能阻止同类事件再次发生,至少下次能够更快地恢复。“快速行动”的心态并不一定与可靠性相冲突。为了使这些理想兼容,Facebook的基础设施提供了安全阀:一个防止快速部署错误配置的配置系统;为客户提供硬化SDK以防止故障的核心服务;一个核心稳定性库,可以防止在发生延迟时资源耗尽。为了应对漏洞,还构建了易于使用的仪表板和工具来帮助查找与故障相关的最新更改。最重要的是,在事件发生后,通过重播吸取的教训将使基础设施更加可靠。参考CoDel(受控延迟)算法;http://queue.acm.org/detail.cfm?id=2209336。立体主义;https://square.github.io/cubism/。嘻哈虚拟机(HHVM);https://github.com/facebook/hhvm/blob/43c20856239cedf842b2560fd768038f52b501db/hphp/util/job-queue.h#L75。节俭框架;https://github.com/facebook/fbthrift。王乐图书馆;https://github.com/facebook/wangle/blob/master/wangle/concurrent/Codel.cpp。https://github.com/facebook/folly/blob/bd600cd4e88f664f285489c76b6ad835d8367cd2/folly/executors/Codel.cpp