来源:混沌工程实践
作者:三水蜗牛 原文作者:Kolton Andrus, Ben Schmaus
标题:Automated Failure Testing, aka Training Smarter Monkeys
出处:Netflix Technology Blog
在Netflix,我们发现主动的故障测试是一个非常棒的方法,帮助我们在生产环境中发现潜在的问题,为我们的用户提供了可靠的产品。
我们做的所有努力(虽然有些是手动的),帮助我们顺利地没有意外地度过了整个假期(如果在除夕之夜被选中值班,那就更好了!😊)。
但是谁喜欢手动的工作呢?另外,我们只测试预期的失败,每次练习通常只针对单个服务或组件进行测试。我们要做得更好!
想象一下,一只猴子在你的代码和基础架构中爬行,注入小的故障并检查其是否导致用户影响。
在探索构建这种猴子的过程中,我们发现了由彼得·阿尔瓦罗(Peter Alvaro)开发的称为Molly的故障测试方法。(http://www.cs.berkeley.edu/~palvaro/molly.pdf)
鉴于我们已经有了一个称为FIT(Fault Injection Test)的故障注入服务,相信我们可以在短期内构建一个原型实现。(https://medium.com/@Netflix_Techblog/fit-failure-injection-testing-35d8e2a9bb2)
我们认为,把Molly论文中阐述的概念应用到大规模生产中,这非常有意义。因此,我们与Peter取得了联系,了解他是否有兴趣共同构建原型。以下就是我们的合作结果介绍。
“依赖树驱动的故障注入(LDFI)逆向地从正确的系统输出中,判定注入故障是否会改变输出结果。”
Molly首先查看成功请求的所有内容,然后问:“哪些因素会改变这个结果?” 我们以下面这个简化的请求为例:
故障集: (A|R|P|B)
一开始,我们假设所有都是必要的因素。然后,假设用户影响是由A或R或P或B故障引起的,其中A代表API(其他以此类推)。我们首先从潜在故障点中随机选择并重新执行该请求,并在选定点注入故障。
三个潜在的结果:
- 请求失败:面临失败(将来的实验需要调整以包含此故障)
- 请求成功:故障点被接管自动恢复了(故障转移或回退)
在此示例中,评价(Ratings)故障,请求成功,从而生成了以下图表:故障集: (A|P|B) & (A|P|B|R)) 由此可了解该请求的更多信息和故障点情况。由于播放列表(Playlist)是一个潜在故障点,因此我们接下来将其失效,生成以下图表:故障集: (A|PF|B) & (A|P|B) & (A|P|B|R)) 这说明了上面第三个潜在的结果。由于执行了故障接管操作(PlaylistFallback),该请求仍然成功。所以,我们有了一个新的故障点需要探索,同时更新实验范围,并进行清理、注入并重复,直到没有更多的故障点可探索为止。Molly对如何探索这个搜索空间没有规定。我们的实现方式是:先列出故障点失效的所有可能性,然后从最小组合中随机选择。例如,[{A},{PF},{B},{P,PF},{R,A},{R,B}…]。探索从所有单点故障开始:A,PF,B;然后继续两个故障的组合,依此类推。利用链路跟踪系统,可为整个微服务构建请求依赖树。通过FIT,我们可用“注入点”的方式获得更多信息。这些都是系统可能发生故障的关键拐点。注入点,包括Hystrix命令执行、高速缓存查找、数据库查询、HTTP调用等等。FIT提供的数据帮助我们构建更完整的依赖树,这就是算法分析的输入。在上面的示例中,我们看到了简单的服务请求树。以下是是利用FIT数据扩展了的请求依赖树:“成功”是什么?最重要的便是用户体验,需要一种衡量标准来反映这一点。为此,我们利用了设备报告的指标集。通过分析这些指标,就可以确定请求是否会产生用户影响。另一种更简单的方法是依靠HTTP状态代码确定成功的结果。但是状态码可能会引起误解,因为某些框架在部分成功时就返回“200”的状态,而在请求内容才放置用户影响的错误描述。目前,只有部分Netflix请求具有相应的设备报告指标。为更多请求类型添加设备报告的指标后,我们就有机会扩展自动故障测试以覆盖更广泛的设备流量。支持回放请求使Molly简单易用,但我们目前做不到。在收到请求时不知道该请求是否等价,不知道是否可以安全地回放。为了弥补这一点,我们将请求分成多个等效类,每个类中的请求“具有”相同的功能,即执行相同的依赖调用并以相同的方式失败。我们先尝试去查看这些请求功能和依赖项之间是否存在直接映射。事实并非如此。接下来,我们探索了使用机器学习来查找和创建这些映射的方法。这似乎是有希望的,但是需要大量的工作才能使其正确。相反,我们将范围缩小到只检查Falcor框架生成的请求。这些请求通过查询参数提供了一组json路径以供请求加载,即“视频”、“配置文件”、“图像”。我们发现这些Falcor路径元素与加载这些元素所需的内部服务一致。未来的工作需要找到一种更通用的方法来创建这些请求类别映射,以便我们可以将测试范围扩展到Falcor请求之外。这些请求类别,会随Netflix工程师编写和部署的代码而变化。为了抵消这种偏移,我们会每天对设备报告的指标集进行抽样,对潜在的请求类别进行分析。无流量的旧类别使之过期,新的代码路径则创建新的类别。请记住,此探索的目标是在故障影响大量用户之前找到它并修复。在运行我们的测试时,引起大量用户影响,这是不可接受的。
为了减轻这种风险,我们重塑了探索方式,实现在任何给定时间段内只进行少量实验。每个实验的范围都只针对一种请求类别,实验运行时间很短(20-30秒),受影响的用户数很小。为了滤除误报,我们会查看实验的总体成功率,超过75%请求的失败情况标记为已发现的失败。由于我们的请求类映射不是完美的,所以我们还会过滤掉由于某种原因没有执行要测试的失败请求。假设我们一天可以运行500个实验。如果我们每次运行都可能影响10个用户,那么最坏的影响是每天有5,000个用户受到影响。但是,并非每个实验都会导致失败。实际上,大多数实验都会成功。如果10%的实验中发现失败(较高的估计值),那么实际上我们每天会影响500个用户的请求,而重试可以进一步缓解。当每天处理数十亿个请求时,这些实验的影响就很小了。我们很幸运,“App Boot”请求,作为最重要的Netflix请求之一,满足了我们的探索标准。该请求会加载运行Netflix应用,以及用户视频初始列表所需的元数据。
对于Netflix而言,这是一个关键时刻,我们希望从一开始就向客户提供可靠体验来赢得他们。这也是一个非常复杂的请求,涉及数十个内部服务和数百个潜在的故障点。对这个空间的蛮力探索需要2^100次迭代(大约30个零),而我们的方法能够在大约200次实验中对其进行探索,并从中发现了五种潜在的故障,其中一种是故障点的组合。好吧,还得手动处理。我们还没有达到能自动修复故障的地步。在这种情况下,我们有一个已知故障点的列表,以及一个允许某些人使用FIT重现故障的“方案”。由此,我们可以验证故障并确定解决方案。我们很高兴能够构建此原型实现,验证实施并使用它来发现真正的故障。我们也希望能够扩展它,以自动方式搜索更多的Netflix请求空间,在真正的故障发生之前,找到更多会产生用户影响的潜在故障点,并解决它们!7月24-25日,IDCF DevOps黑客马拉松·北京,等你来挑战!!👇