您还未登录! 登录 | 注册 | 帮助  

您的位置: 首页 > 软件开发专栏 > 开发技术 > 正文

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

发表于:2022-05-11 作者:崔皓 来源:51cto

作者 | Kislay Verma

译者 | 崔皓  

审校 | 云昭

 

开篇 

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

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

图 组件之间的关系

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

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

PART 02

最小化组件间的依赖

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

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

1、副本数据

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

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

2、非规范化数据

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

3、组件库

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

 

隔离错误  

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

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

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

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

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

 

保护调用者

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

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

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

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

 

保护被调用者

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

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

 

在系统中建立缓冲区

1、异步通信

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

2、弹性供应

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

原文链接:

​https://kislayverma.com/software-architecture/building-robust-distributed-systems/​

译者介绍

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