如何避免大规模线上故障现象,如何避免线上线下渠道冲突
chanong
|作者| 曹春辉编辑| 欧阳淑丽
“大规模失败”是2015 年Facebook acm 队列帖子。主要描述网上常见问题及解决方案,比较实用。
“如果你不害怕,你会做什么?”和“财富眷顾勇敢的人。”是FB 的企业口号,它们就挂在墙上。
为了在快速变化的系统中稳定FB 系统,工程师创建了系统故障的摘要和摘要。构建可靠的系统需要了解故障。为了了解故障,工程师构建了诊断问题的工具,并建立了审查事件以避免未来再次发生的文化。
事故大致可分为三类。
为什么会发生失败
单台机器故障单台机器上的孤立故障通常不会影响基础设施的其他部分。这里的单机故障是指机器的硬盘故障,或者特定机器上的服务在其代码中遇到错误,例如内存损坏或死锁。
自动化是避免个别机器故障的关键。通过总结已知的故障模式并将其与未知故障症状的调查相结合。当发现未知故障的症状(例如响应时间缓慢)时,将删除该机器,进行离线分析,并聚合为已知故障。
工作负载变化现实世界的重大事件可能会给Facebook 的网站基础设施带来压力,包括:
奥巴马当选总统后,他的Facebook 页面的活动达到了创纪录的水平。
超级碗或世界杯等大型体育赛事的高潮可能会导致极高的发帖量。
新功能发布时的负载测试和引流阶段对用户来说是不可见的,但会产生流量。
在这些事件期间收集的统计数据为系统设计提供了独特的视角。重大事件导致用户行为发生变化,这些变化可以为系统后续决策提供数据基础。
人为错误
图1a 显示了周六和周日的事件数量如何显着下降,尽管网站的流量整周保持不变。图1b 显示,六个月内只有两周没有发生事故。这是圣诞节的一周,员工们需要互相撰写同行评审。
从上图可以看出,大多数故障都是由人为因素造成的,因为事件统计数据与人类活动模式相符。
3 失败原因
失败的原因有很多,但最常见的有以下三种。我们将解释此处列出的每项预防措施。
配置更改的快速部署配置系统通常设计为在全球范围内快速复制更改。快速配置更改是一个强大的工具,但快速配置更改可能会在部署有问题的配置时导致事故。以下是防止配置更改失败的一些方法。
每个人共享相同的配置系统。通用配置系统可确保程序和工具适用于所有类型的配置。
配置更改的静态检查。许多配置系统允许松散类型的配置,例如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工程师研究了缓冲区膨胀问题。这里的问题与缓冲区膨胀问题非常相似,都不会在拥塞时导致过度延迟。这里实现了CoDel(受控延迟的缩写)算法的变体。
注:虽然里面写了M和N,但M和N实际上是固定值,N=100ms,M=5ms
onNewRequest(req,queue):ifqueue.lastEmptyTime()(now-N秒){timeout=Mms}else{timeout=N秒;}queue.enqueue(req,timeout)这个算法确保队列已经过去100ms如果在该时间内没有清除,则在队列中花费的时间限制为5ms。如果服务能够在最后100 毫秒内清除队列,则在队列中花费的时间将限制为100 毫秒。该算法在减少队列的同时,减少了排队时间,保证了可靠性(由于lastEmptyTime已经过去很久了,导致了5ms的队列超时)。如此短的请求超时似乎有悖常理,但如果系统无法跟上传入请求的速率,此过程允许快速丢弃请求,而不是堆积请求。缩短超时可确保服务器始终接受比其实际处理能力多一点的工作,因此它永远不会闲置。
如上所述,这里的M和N基本上不需要调整以适应场景。解决队列问题的其他方法(例如限制队列中的项目数或设置队列超时)必须根据您的场景进行调整。 M 固定为5 ms,N 设置为100 ms,适用于大多数场景。 Facebook的开源Wangle库和Thrift都使用了这种算法。
自适应LIFO(后进先出)。大多数服务按照FIFO 顺序处理队列。然而,当队列较长时,先入请求往往会等待很长时间,用户可能会放弃与请求相关的操作。目前,处理第一个排队的请求会消耗不太可能使用户受益的请求的资源。 FB 的服务使用自适应后进先出算法处理请求。在正常操作条件下,请求以FIFO 模式处理,但当队列开始建立时,它会切换到LIFO 模式。如图2 所示,自适应LIFO 和CoDel 可以很好地协同工作。 CoDel 设置较短的超时以防止形成长队列,自适应后进先出模式将新请求放在队列的前面,从而最大限度地提高满足CoDel 设置的最后期限的机会。 HHVM 实现了这种LIFO 算法。
并发控制。 CoDel 和Adaptive LIFO 都在服务器端执行。服务器是减少延迟的好地方。服务器为大量客户端提供服务,并且拥有比客户端更多的信息。但是,有些故障很严重,可能会导致服务器端控制无法启动。事实上,FB 也在客户端实施政策。也就是说,每个客户端跟踪每个服务的未完成出站请求的数量。当提交新请求时,如果服务的待处理请求数量超过可配置的数量,则该请求立即被标记为错误(注意:这应该类似于断路器)。这种机制可以防止单个服务垄断客户的所有资源。
帮助诊断问题的工具
即使采取了最好的预防措施,故障仍然可能发生。在停机期间使用正确的工具可以快速确定根本原因并最大限度地减少停机时间。
最重要的是,通过审查吸取的经验教训将提高事故发生后基础设施的可靠性。
参考CoDel(延迟控制)算法;http://queue.acm.org/detail.cfm id=2209336。
立体主义;https://square.github.io/cubism/。
HipHop 虚拟机(HHVM);https://github.com/facebook/hhvm/blob/43c20856239cedf842b2560fd768038f52b501db/hphp/util/job-queue.h#L75。
Thrift 框架;https://github.com/facebook/fbthrift。
Wangle 库;https://github.com/facebook/wangle/blob/master/wangle/concurrent/Codel.cpp。
https://github.com/facebook/folly/blob/bd600cd4e88f664f285489c76b6ad835d8367cd2/folly/executors/Codel.cpp








