我用OpenCcount分布式跟踪解决了Jepsen:个人之旅

2020-06-05 03:36:34

软件的强大程度取决于编写它的工程师。这篇文章献给所有在编写和调试系统方面付出了不懈努力的工程师,他们使系统稳定可靠,可供他人使用。

那一年是2018年。当时是晚上9点。我早上8点开始了我的一天。这种特殊的杰普森测试是不可饶恕的。我不明白为什么这个杰普森测试会继续失败。就在前一天,这项测试已经通过了16次中的16次。

杰普森产生了随机性和混乱。这意味着测试有时可以通过,但这并不意味着没有错误。但是,如果它哪怕失败一次,那就证明存在错误。确定修复的一种更确定的方法是反复循环测试。16分中有16次传球是一个相当鼓舞人心的分数。

在旧金山金融区租来的一间小办公室里,我疲惫不堪,孤身一人。这不是你在WeWork广告中看到的典型的玻璃墙办公室,这是雷格斯,厚厚的墙壁上有一扇木门。办公空间让你感到与世隔绝--你可以在里面坐上一整天,看不到其他任何人。

几个月前,我创建了一家名为f*ck-the-bank的分支机构,并对此测试揭示的问题进行了可能的修复。这个名字是由于杰普森测试而产生的,被称为银行测试。

Jepsen是一个带故障注入的分布式系统验证框架。Jepsen测试是一个Clojure程序,它使用Jepsen库来设置分布式系统,对该系统运行一系列操作,并验证这些操作的历史记录是否有意义。

Jepsen已经被许多分布式事务数据库用来验证它们的一致性保证,不仅在健康的集群中,而且在进程崩溃、网络分区、时钟偏差等一系列故障情况下也是如此。

Dgraph是第一个被截获的图形数据库。事实上,对于一家(当时)种子阶段的公司来说,聘请业内资深人士凯尔·金斯伯里(Kyle Kingsbury)(又名阿菲尔)帮助寻找Dgraph中新建的分布式交易系统的问题,是一件非常了不起的事情。该过程确定了23个问题,在协作结束时,我们解决了其中的19个问题。然而,最后四场比赛都非常具有挑战性。其中最复杂的是银行测试。

银行测试从单个账户中的固定金额(100美元)开始,然后在账户之间随机转账。转账通过按密钥读取两个随机帐户并为这些帐户写回新的金额来进行。同时,客户端读取所有帐户以观察系统的总体状态。在整个测试过程中,所有账户余额的总和应该保持不变。

然后,将Bank测试与各种杀死进程、移动碎片并引入网络分区和时钟偏差的漏洞结合在一起。

如果数据库的事务完整性和数据一致性存在弱点,此测试肯定会找到它。对Dgraph来说也是如此。

在过去的几个月里,我已经投入了所有的精力来修复这些测试失败。团队留下的代码一团糟:在补丁上添加补丁来修复每个工程师遇到的个别问题,而不是在适当的位置挑战整体设计。

我首先试着理解所有这些乱七八糟和简单化背后的原因。但后来意识到,这一切都是基于旧的假设,不再正确。没有人用对交易系统的进化理解来挑战潜在的假设。通过重新设计设计,可以极大地简化代码。所以,在过去的几个月里,我从头开始重写了整个内容。

重新编写、简化的代码库确实修复了各种Jepsen问题。最后一个主要据点是银行测试。

那天晚上,在第16次重新测试通过后,我欣喜若狂。我的最后一次修复一定解决了这个问题。为了确认,我将其设置为通宵运行。我迟到了,对终于解决银行测试发现的漏洞的可能性感到兴奋。我家离办公室步行10分钟。当我到家时,我的妻子和女儿已经熟睡了。我把晚餐重新加热,把餐桌放在厨房的柜台上,然后就上床睡觉了。

第二天早上,我很早就起床了,在路上从咖啡厅拿了一杯克里斯托,然后冲到办公室。当时是早上8点。我迫不及待地登录到我的桌面上,查看我一夜之间运行的测试结果。结果,第一次运行本身就失败了。它没有费心在前半小时之后继续。

到了晚上9点,我已经竭尽全力准备考试了。筋疲力尽,士气低落,我意识到我仍然没有解决的办法。这次考试真的让我精疲力尽。

Jepsen是一个令人难以置信的测试框架。然而,它本质上是黑盒测试。虽然它确实会生成它所采取的操作的日志,但是很难将这些操作与系统内部发生的事情关联起来。

查看Jepsen或Dgraph日志不足以调试该问题。这充其量只是一种猜测。此外,Jepsen是用Clojure编写的,这使得调整代码库以引入更多可见性变得更加困难。Clojure开发人员很难找到(现在我们的团队已经发展到30多名工程师,我们终于有一个会说Clojure的人了)。

回到谷歌,我听说过分布式跟踪。Dapper被称为Dapper,它帮助提供了有关复杂分布式系统行为的更多信息。它帮助解决了由多个服务器处理的请求所看到的延迟和其他问题。

我自己从来没有用过Dapper。我的团队负责为整个Web构建实时增量索引系统,他们正在进行本地跟踪,即针对单个服务器的跟踪。因为我们正在运行数千台服务器,所以即使是最罕见的跟踪也可以通过对这些服务器上的实时运行请求/rpcz进行实时爬行来找到。

Dgraph也在做类似的事情。我们使用的是golang.org/x/net/trace,它可以提供机器本地跟踪,但不会跨服务器跟踪单个请求。虽然本地跟踪对于了解系统中发生的一般情况很有用,但与查询的分离导致它们无法有效地理解特定事务行为不正常的原因。

我知道一个开源的分布式跟踪框架,我觉得把它集成到Dgraph中会很有用。但是,这项任务被列入了越来越多的Backburner项目的长名单中。我们有了新的客户,财富500强的大公司纷纷来敲我们的门,我几乎没有时间花在那些毫无根据的实用的想法上。

但到了晚上9点,很明显,在黑暗中拍摄只能带你走得更远。如果我必须在Jepsen测试失败的情况下继续取得进展,我需要一种更好的方式来阐明每个事务请求发生了什么。我需要一个灯笼。我需要那个追踪框架。

开放普查(现在合并成开放遥测)是一组库,允许你收集分布的踪迹,然后将数据实时传输到后端。

OpenCcount的工作原理是有一个唯一的请求ID。即使请求通过网络,也会维护此ID。在GO中,这是通过在上下文中编码该id来实现的,该id由GRPC传递。参与处理此请求部分的多个服务器都会将其执行跟踪部分发送到公共后端。然后,该后端将缝合各个部分,以向您显示跨所有服务器的整个请求执行的跟踪。

这些请求中的每一个都有多个跨度。每个范围都可以用有用的信息进行注释,以帮助您调试正在发生的事情。

在9e7fa0中,我用OpenCcount span替换了大部分的网络/跟踪跟踪。特别是,我添加了注释,这样我们就可以根据事务开始时间戳搜索任何跟踪。

上面显示了从Jepsen到Jaeger中的Dgraph的请求跟踪。这是我们最喜欢的分布式跟踪系统来查看这些痕迹。事实上,Jaeger最近推出了獾支持,这让我们更喜欢和推荐DIT。

在第一次OpenCcount集成后的一周内,我能够确定银行测试失败的原因。它发生在网络分区期间。正如我在提交消息中注意到的那样。

开放普查和Dgraph调试解剖有助于确定该违规的原因。说明问题的标志是注意到倒数第二个提交之一的/COMMIT超时,这是对违反提交的提交。

违规的原因(可能很难跟踪,可以随意跳过),如提交消息中所述,或者由Aphyr在他的新报告3.3节中稍好地解释如下:

当零引导者接收到对事务T的提交请求时,它会将atimeStamp分配给该提交。如果Zero无法与其RAFT对等节点通信,而一个新的Zero节点成为领导者,那么该新领导者将开始以明显更高的数字分配时间戳。与新的Zero领导者交互的Alpha将提前它们的最大应用时间戳以匹配。

然后假设最初的Zero Leader作为跟随者重新加入集群,并重试其提交建议-这一次,成功了。由于此新提案保留了原始事务时间戳,因此可能会出现两个问题:

在新领导推进时钟之后,但在T的提交被重试之前执行的读R可能无法观察到T-即使T会继续在R的逻辑过去中提交。本质上,这允许事务的时间线上暂时的“空洞”。

当Alpha节点对关键字k应用写入w时,它将首先检查k的最后写入时间戳,如果它较低,则忽略w。如果w是来自逻辑过去的写入,则w可能会被拒绝-但是来自同一事务的其他写入可能会成功,只要它们不是最近写入的。这允许Dgraph部分应用事务。

这是一个复杂的发现。但是,使用OpenCcount作为灯笼照亮分布式事务采取的每条路径,有助于快速确定原因。

修复方法是不允许零1筏子跟随者向领先者转发提案。所以,只有零领袖才能提议筏子。这样,如果一位领导人下台,同时在循环中获得提案的法定人数,该提案就会失败-这是这里为了避免交易性担保失败而打算采取的行为。

你会问,为什么要循环运行提案呢?为什么不在第一次失败时就放弃呢?Dgraph正在使用的RAFT库可以放弃建议,而不会返回错误或任何其他可识别的指示。因此,如果在发送建议后,您在一段时间后没有在提交的日志中看到它,您必须重试。但是,由于活跃度高,消息也可能会延迟。因此,Dgraph会进行指数回退重试,直到最终在另一端看到建议。

Dgraph允许自动分片迁移来平衡服务器之间的数据负载,这是很少见的,正如您可以想象的那样。然而,Jepsen专门针对这一特性,以尽可能快的速度移动碎片来引入事务违规。

问题2321捕获检测到的错误。有了开放人口普查,原因就变得一目了然了。我看到Alpha服务器为碎片S中的数据请求提供服务,即使在S被移动到另一台服务器之后也是如此。

在Dgraph中,Zero保存有关哪个Alpha服务器服务哪些数据的成员身份信息的真实来源,并将其传输到集群中的所有Alpha。在本例中,TheShard移动和Alpha服务器从Zero了解它之间存在竞争条件。因此,Alpha服务器在完成移动后可能最终服务于请求,认为它仍在为该碎片提供服务。

更笼统地说,平板电脑迁移容易出错,因为更改分布式状态非常困难。Dgraph依赖于RAFT来实现组内的状态变化,RAFT是一种可靠的算法,实现成熟,能够很好地解决故障。Dgraph通过让节点就哪些组拥有哪些平板电脑达成一致,来协调RAFT组之间的事务。这也是相对简单的-只要映射没有改变。当映射稳定时,每个人的请求都会到达正确的组,RAFT从那里处理它。当映射更改时,节点可能会过时:出于性能原因,Dgraph不会使用一致的协议进行平板映射。取而代之的是,节点通过旁路异步发现映射更改,这使得协议更加棘手。

上述两次修复都是从2018年底到2019年初。OpenCensus集成帮助识别了这些复杂的问题,这是任何猜测置换都无法实现的。

开放人口普查福利并没有止步于此。甚至在更近的2020年3月,我们修复了代码库中的一个两年前的错误,导致在非常特定的场景下对数据返回零响应。

除了帮助我们识别和修复bug之外,每当我们的客户遇到查询性能问题时,调试过程中的一个重要步骤就是要求他们提供查询跟踪。它揭示了该查询到底发生了什么,成为瓶颈的任何步骤,并可以告诉我们系统中发生了什么,这让我们可以快速找到问题。

OpenCcount在调试Dgraph方面最有可能成为一项优势。尽管引入分布式跟踪只是为了帮助解决Jepsen问题,但它已经成为Dgraph调试工作流程的核心部分。

正如阿菲尔所说,未能检测到错误的存在并不等同于没有错误。

我们无法证明其正确性,只能表明系统发生故障的可能性较小,因为到目前为止,我们还没有观察到问题。-https://jepsen.io/ethics。

然而,我要补充的是,不能在Jepsen生产的那种极端测试下发现错误,确实激发了人们对软件稳定性的信心。最后,添加质量测试是改进软件的最佳选择。

Aphyr最近在Dgraph上完成了另一轮测试,发现Dgraph解决了2018年早期报告中的所有问题,但继续发现了一些与平板电脑移动相关的问题。

只是为了解决这些问题:在Dgraph中,平板电脑的移动并不频繁。杰普森的行动速度很快,很不自然。考虑到杰普森的工作是确定系统是否在极端情况下崩溃,这是完全意料之中的,但它们影响Dgraph用户的可能性很小。因此,虽然我们需要调查和解决这些问题,但考虑到我们正在做的所有其他事情,他们的优先事项是很低的。

但我毫不怀疑,当我们真的到达它们的时候,OpenCcount会让人们识别问题并迅速解决它们。

这就是难做的事情的难点--没有处理它们的公式。--本·霍洛维茨。

2018年是艰难的一年。从早上8点工作到晚上9点很辛苦。构建分布式图形数据库很困难。让它拥有分布式事务是很困难的。拥有那个赛普·杰普森是很难的。但是,这就是为什么你要捡起难造的东西--恰恰是因为它们很难造。

Dgraph在2018年所做的所有努力都使它成为我们可以自信地支持Dgraph一致性保证的地方。所以,当你筋疲力尽、士气低落、孤身一人的时候,继续努力吧!

另外,我要感谢Emmanuel帮助将OpenCensusTM与Dgraph集成,感谢Kyle将Jepsen测试与Dgraph集成,感谢Kit将OpenCcount与Jepsen集成,并感谢所有Dgraph贡献者帮助构建了这个令人惊叹的软件。

上图:SpaceX猎鹰9号火箭搭载着该公司的乘员龙飞船,在NASA的SpaceX Demo-2国际空间站任务中从发射综合体39A发射升空。

如果你对Dgraph的架构感兴趣,你可以阅读这篇文章,它详细介绍了Dgraph的内部工作原理。↩︎