vlambda博客
学习文章列表

如何构建高可用的分布式系统?

译者 | 崔皓 

审校 | 云昭




PART 01

    开篇    




本文讨论分布式系统如何对随机故障进行弹性处理,这个问题非常重要,因为随着系统规模的增大,随机故障会变得越来越普遍。


系统理论告诉我们,系统中相互关联的部分越多,发生故障的可能性就越大。因此,要构建一个弹性系统,我们需要减少这种相互关联。否则就需要通过 “临时”切断与故障点的连接来避免问题的发生,从而保证不会因为该故障点而殃及其他节点的正常运行。也就是我们通常所说的,针对分布式系统进行降级和熔断。


图1 组件之间的关系


一般而言我们会基于如下假设:在分布式系统中的任意组件在任何时刻都有可能发生故障,并且需要定义出现故障的时候对应的处理方式。


最后,我们需要在系统中创建缓冲区——提供一些宽松的手段,也就是在不能完全消除对故障组件的依赖时,采取更加柔和方式处理意外情况。




PART 02

最小化组件间的依赖




分布式系统的组件通过相互通信的方式获取数据和调用功能。基于这两种情况,都可以通过将数据/功能推送到调用组件而不是通过远程访问来获得,从而降低组件之间的连接需求。


构建大规模分布式系统迫使我们放弃标准软件工程的许多“最佳实践”。关键是,当采用分布式系统的复杂性来换取系统的可扩展性时,需要尽可能地控制组件的“分布”。以下几个要点是需要关注的:


1、副本数据


如果经常从一个组件访问某些数据,就可以复制该组件,而不必在运行时检索它。这种方式可以大大减少运行对该组件的依赖,并降低访问组件的延迟。


对于经常访问且有规律性变化的数据而言,可以通过临时缓存并配合定期缓存刷新的方式来获取他们。更改频率更低或从不更改的数据(例如客户姓名)可以直接存储在组件中。不过当这些数据发生变化时,就需要做一些额外的工作对其进行更新,不过这种小开销对于整个系统而言是值得的,因为提升了系统的弹性。


2、非规范化数据


非规范化数据以一种特殊形式的副本存在于组件中。如果使用关系数据存储数据,可以通过在主实体中复制数据的方式来降低查看多个实体的成本。对于本地化分散数据从而获得更好性能,也适用于此种方式。


3、组件库

为了减轻组件的功能依赖性,可以将远程组件打包成组件库,并将其嵌入到的需要调用该库的组件中。这种方式并不是总是可行(存在跨语言调用,或者因为组件太大而不能打包成库),因此会带来一系列问题(例如:某个功能以来多个组件库,一旦功能发生变化就需要对多个组件库进行升级)。话说回来,如果功能很关键且被经常访问,组件库的方式就可以减少组件之间的链接,让被依赖的组件库成为调用组件的本地方法。




PART 03

 隔离错误 




错误隔离的功能非常重要,原因有二。其一,是个别错误在分布式系统中非常突出。(许多移动部件的简单功能)。其二,是如果不能防止错误通过级联的方式影响整个系统,那么就无法构建复杂系统架构。


错误隔离的由 SLA(Service Level Agreement 服务保障协议)构成。它针对每个组件声明了质量参数,在组件执行过程时会参照这些参数。质量参数包括:延迟、错误率、并发性等。


当一个组件调用进行SLA设置的组件时,调用方会根据被调用方的SLA参数进行设置,其目的是在调用失败的时候可以采取对应的处理措施。例如,调用方发现被调用方的错误率到达了70%,就会延迟5s再调用该组件。


如果被调用方的组件检测到无法维护自身的SLA,它可以先发制人地告诉其调用者后退并稍后再回来。即,一旦被调用方自己感觉不太好了,就告诉调用方你等下再来访问。


同时为了保持整体系统的健康,最好使用快速失败的方式告知调用方,而不是在违反 SLA 的情况下让调用方成功调用。需要注意的是,调用方和被调用方都必须进行该设置。




PART 04

保护调用者




超时:如果被调用的组件在其 SLA 设置范围内没有响应,调用者必须设置对应的超时机制,或放弃又或者采用回退机制(即使抛出错误),从而维护自己的 SLA 并防止一连串的 SLA 违规现象的发生。


重试:网络不可靠性会引发分布式系统中的随机错误。假设在调用者自身的 SLA设置允许的情况下,可以进行重试操作。重试操作的前提是被调用者需要支持操作的幂等性。即不论进行多少次操作对数据状态的改变也只算做一次。


断路器:如果调用连续失败,调用者可以通过“打开电路”的方式切断连接并停止调用一段时间。由于调用者针对错误场景有备份方案,从而可以节省调用者宝贵的资源,否者这些资源就会被浪费掉。停止调用还可以减少被调用组件的负载,给其留有喘息的余地。


“断路器库”会对有问题的组件进行定期轮询,并在其可能恢复性能时重新启动调用机制。




PART 05

保护被调用者




随机补偿:虽然重试可以减少调用错误发生的概率,但对于被频繁调用的组件而言,出现小小的性能问题都会导致调用者进行重试操作。在调用者足够多的情况下,就会形成 “重试风暴”,其结果会造成负载峰值并不利于被调用者的恢复。为了防止这种情况的发生,应该设置随机的重试时间间隔,使重试负载交错进行。


背压:如果一个组件检测到自己承受过多的负载并且即将违反其 SLA,它可以抢先开始丢弃新请求,直到其性能得到控制。这比接受它知道它不能在 SLA 内提供服务或没有完全崩溃风险的请求要好得多。




PART 06

在系统中建立缓冲区




1、异步通信


可以通过消息总线之类的异步通信方式,调用远程组件而无需非常严格的 SLA 参数规则。消息总线种存放要访问被调用方的请求,被调用组件准备好之后再处理这些请求,而不是立即处理请求,这样系统就可以更加灵活地处理负载了。


2、弹性供应


可扩展性也可以通过利用硬件扩展的方式实现。如果系统规模不断增长,就需要分配更多硬件资源满足系统的需求。虽然这种扩展方式需要考虑成本,并在我们能够承受的范围里实现,但它也为抵御不可预测负载提供了最后一道防线。


原文链接:
https://kislayverma.com/software-architecture/building-robust-distributed-systems/



译者介绍


崔皓,51CTO社区编辑,资深架构师,拥有18年的软件开发和架构经验,10年分布式架构经验。曾任惠普技术专家。乐于分享,撰写了很多热门技术文章,阅读量超过60万。《分布式架构原理与实践》作者。


今天因为你的点赞,让我元气满满!