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

如何构建健壮的分布式系统_0

时间:2023-03-17 16:43:13 科技观察

我之前在这个博客上写过分布式系统是什么以及它们如何以不得不处理更复杂的系统设计为代价为我们提供巨大的可扩展性。让我们讨论如何使分布式系统对随机故障具有弹性,随机故障随着系统变大而变得更加普遍。系统理论告诉我们,系统中相互关联的部分越多,发生灾难性故障的可能性就越大。因此,要构建弹性系统,我们需要减少连接数。如果这不可能,我们需要实施“暂时”切断与故障部分的连接的方法,以便错误不会级联到其他部分。每个组件都必须假设所有其他组件都会在某个时刻发生故障,并决定在这些故障发生时它会做什么。最后,我们需要在系统中建立一些缓冲区——一些放松的方式,如果不取消对它的要求,那么就有时间处理意外情况。1.最小化组件间的依赖性。分布式系统的组件相互通信以获取数据或功能。在这两种情况下,我们都可以通过将数据/函数推送到调用组件而不是远程访问来减少连接要求。构建大型分布式系统迫使我们放弃标准软件工程的许多“最佳实践”。要记住的关键是,当我们利用分布式系统的复杂性来实现可伸缩性时,我们还需要尽可能地控制“分布”。1.1复制数据如果我们经常从另一个组件访问一些数据,我们可以在我们的组件中复制它,而不必在运行时检索它。这可以大大减少运行时依赖性,并有助于改善我们组件的延迟。频繁访问但定期更改的数据可以通过定期缓存刷新来临时缓存。更改频率较低或从不更改的数据(例如客户姓名)可以直接存储在我们的组件中。如果/当这些数据发生变化时,我们可能不得不做一些额外的工作,但是为了提高弹性,这种增加的小开销通常是值得的。1.2非规范化数据非规范化是组件内发生的一种特殊形式的复制。如果我们使用关系数据存储,我们可以通过复制主实体中的数据来降低查看多个实体的成本。本地化分散数据以获得更好性能的原则也适用于此。1.3库为了减轻对另一个组件的功能依赖,我们可以将远程组件打包为库并将其嵌入到我们的组件中。这并不总是可能的(它可能是用另一种语言编写的,或者太大而不能成为一个库)并且会带来一系列问题(功能的变化需要跨多个组件升级库),但是如果功能是关键的并且经常被大规模访问,这是断开组件之间的连接并使它们本地化的可行方法。2隔离错误错误隔离很重要有两个原因。一是个别错误在分布式系统中更为常见(具有许多活动部件的简单功能)。另一个是,如果我们不能在整个系统中防止连锁错误,那么我们就失去了建造复合体的初衷。错误隔离的主要构造是SLA。每个组件都声明了一些在执行功能时会遵守的质量参数。这些参数可以包括延迟、错误率、并发性等。在这个SLA之外,调用它的组件假定它已经失败并且需要自己采取适当的行动。如果组件本身检测到它无法维护其SLA,它可以先发制人地告诉其调用者暂停并稍后再次调用。为了保持整体系统健康,快速失败比成功应对SLA违规要好。两个组件(一个调用和一个诱发)都必须为此设置机制。2.1保护调用者免受超时:如果被调用的组件没有在其SLA内响应,调用者必须超时(放弃)并使用某种回退机制(即使它抛出错误)来维护自己的SLA并防止级联违反SLA。重试:由于网络不可靠,分布式系统中的许多错误只是随机的。如果调用者自己的SLA允许,调用者可以重试该操作。重试的前提是操作的幂等性。即它不应该改变状态或只做一次,即使它被调用两次。断路器:如果对某个组件的调用连续失败,调用者可以“开路”切断连接,停止调用一段时间。由于调用者已经对某些错误场景具有备份行为,因此这为调用者节省了宝贵的资源,否则这些资源将被浪费。停止调用还可以减少被调用组件的负载,并为其提供一些恢复空间。断路器库具有定期轮询有问题的组件并在其性能似乎已恢复正常时重新启动调用进程的机制。2.2保护以随机间隔调用:虽然重试可以减少错误,但频繁使用的组件中的小性能问题可能会导致其所有调用者立即重试。这种“重试风暴”可能会导致负载激增并阻止该组件恢复。为了防止这种情况,重试之间应该有一个随机间隔,以便负载交错。背压:如果一个组件检测到它承受的负载太大并且即将违反其SLA,它可以先发制人地开始丢弃新请求,直到它的性能得到控制。这比接受它知道无法在SLA范围内交付或冒着完全崩溃的风险的请求要好得多。3在系统中构建缓冲区3.1异步通信消息总线等异步通信通道允许在没有非常严格的SLA依赖性的情况下调用远程组件。通过让被调用的组件准备消息而不是立即使用它们,系统变得更加灵活,可以满足增加的工作负载的需求。3.2弹性配置可扩展性最终归结为充分利用可用硬件。但是,如果您看到规模增长,让您的系统松一口气的简单方法是分配更多硬件。虽然这只有在我们能够负担得起的成本范围内才可行,但它为我们提供了最后一道防线,以防止不可预测的负载变化。