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

分布式系统的代码检查清单

时间:2023-03-16 00:57:07 科技观察

微服务架构是软件工程社区广泛采用的一种实践。采用这种架构风格的组织发现自己要处理分布式故障增加的复杂性(除了实现业务逻辑的复杂性之外)。分布式计算的谬误有据可查,但很难发现。因此,构建大规模、可靠的分布式系统架构是一个难题。作为必然结果,当我们将网络交互的复杂性引入网络时,在非分布式系统中看起来不错的代码可能会成为一个巨大的问题。在多年遇到生产代码中的故障模式并将它们植根于各种代码之后,我(和许多其他人一样)开始识别一些更常见的故障模式。这些在公司和语言堆栈之间略有不同(取决于内部基础设施和工具的成熟度),但其中一个或多个通常是生产问题的原因。这里有一些代码检查指南,它们是我在分布式环境中检查与系统间通信相关的代码的基本清单。并非所有这些方法都适用,但它们都是非常基本的问题,因此我发现机械地为该列表添加下标、标记缺失的项目以供进一步讨论是有用且令人放心的。从这个意义上说,这是一个你可能想要一直坚持的愚蠢清单。调用远程系统时,远程系统出现故障时会发生什么?无论系统设计的维护程度如何,它都会在某个时刻出现故障——这是在生产环境中运行软件的事实。它可能由于错误、某些基础设施问题、流量突然激增或疏忽的缓慢衰减而失败,但它失败了。调用者如何处理这种失败将决定整个架构的弹性和健壮性。定义错误处理路径:你的代码中必须有一个定义明确的错误处理路径,而不是让你的系统在最终用户面前崩溃。无论是设计良好的错误页面,带有错误指标的异常日志,还是带有回退机制的断路器,都必须明确地处理错误。创建一个恢复计划:考虑代码中的每个远程交互,并找出我们需要做什么来恢复被中断的工作。我们的工作流是否需要是有状态的,以便它们可以从故障点触发?我们是否将所有失败的有效载荷发布到重试队列/数据库表中,并在远程系统恢复时重试它们?我们是否有脚本来比较两个系统的数据库并以某种方式同步它们?在部署实际代码之前,应该实施和部署一个明确的恢复计划,最好是一个系统。当远程系统变慢时会发生什么?这比完全失败更隐蔽,因为我们不知道远程系统是否正常工作。为了处理这种情况,应始终检查以下内容。始终为远程系统调用设置超时:这包括远程API调用、事件发布和数据库调用的超时。我在如此多的代码中发现了这个简单的缺陷,它同时令人震惊,但并非不可预见。检查是否为调用中的所有远程系统设置了有限且合理的超时,以避免在远程系统由于某种原因变得无响应时浪费资源等待。超时重试:网络和系统是不可靠的,重试对于系统弹性来说是绝对必要的。重试通常会消除系统间交互中的许多“漏洞”。如果可能,在重试中使用某种退避(固定的,指数的)。在重试机制中添加一点抖动会使事情变得有点喘不过气来。如果负载较重,将被调用系统放置在房间内可能会提高成功率。重试的另一面是幂等性,我们将在本文后面介绍。使用断路器:预打包的此功能的实现并不多,但我看到公司在内部编写自己的包装器。如果你有这个选择,一定要练习。如果您不这样做,请考虑投资建造它。有一个明确定义的框架用于在发生错误时定义回退,并且这是一个很好的先例,不要将超时视为失败-超时不是失败,而是非确定性条件,并且应该以支持解决非确定性的方式完成确定性处理。我们应该建立明确的解决机制,以便系统在超时的情况下保持同步。从简单的协调脚本到有状态的工作流再到死信队列等等。以受控方式使用批处理:如果您正在处理大量数据,请进行批处理远程调用(API调用、数据库读取)而不是一个接一个地调用,以消除网络开销。但请记住,批处理大小越大,整体延迟就越高,可能失败的工作单元就越大。因此,批处理针对性能和容错进行了优化。在构建其他人将调用的系统时,所有API都必须是幂等的:这是重试API超时的反面。仅当您的API可以安全重试并且不会导致意外副作用时,调用方才可以重试。我所说的API是指同步API和任何消息传递接口——客户端可以将相同的消息发布两次(或者代理可以发送两次)。清楚地定义响应时间和吞吐量SLA,以及遵循它们的代码:在分布式系统中,快速失败比让调用者等待要好得多。诚然,吞吐量SLA很难实现(分布式限速本身很难解决),但我们应该认清自己的SLA,规定如果要解决调用可以主动挂断。另一个重要方面是了解下游系统的响应时间,以便您可以确定系统的最快速度。定义和限制批处理API:如果要公开批处理API,则应明确定义最大批处理大小并受我们所需的SLA限制。这是遵守SLA的必然结果。预先考虑可观察性:可观察性意味着能够分析系统的行为而无需查看系统内部。预先考虑您应该收集有关系统的哪些指标以及您应该收集哪些数据可以让您回答以前没有问过的问题。然后对系统进行检测以获得该数据。这样做的一个强大机制是识别系统的域模型并在域中每次发生事件时发布一个事件(例如,收到请求id123,返回请求123的响应——注意如何使用两个“域”事件称为“响应时间”的新指标。原始数据>>预先确定的聚合)。通用指南积极缓存:网络是易变的,因此缓存尽可能多的数据,尽可能接近数据的使用。当然,你的缓存机制也可以是远程的(例如在另一台机器上运行的Redis服务器),但至少你可以将数据带入你的控制域并减少其他系统的负载。考虑失败单元:如果API或消息代表多个工作单元(批次),那么失败单元是什么?整个有效负载是否应该同时全部失败,或者各个单元能否独立成功或失败。在部分成功时,API是否以成功或失败代码响应?在系统边缘隔离外部域对象:从长远来看,这是我看到的另一个麻烦。我们不应该以重用的名义跨系统使用其他系统的领域对象。这将我们的系统耦合到另一个系统的实体建模,并且每次另一个系统发生变化时我们都会进行大量重构。我们应该始终构建自己的实体表示,并将外部有效负载转换为该模式,然后在系统内部使用它。我希望您发现这些指南有助于减少分布式系统代码中最常见的错误。我想听听您是否认为其他一些注意事项易于应用但非常有效-我们可以在此处添加它们!