草庐IT

架构篇-怎么构建健壮的分布式系统?

攻城狮大兵 2023-03-28 原文

我之前在这个博客上写过什么是分布式系统,以及它们如何以不得不处理更复杂的系统设计为代价为我们提供巨大的可扩展性。让我们讨论一下如何使分布式系统对随机故障具有弹性,随着系统变大,这种随机故障会变得越来越普遍。

系统理论告诉我们,系统中相互关联的部分越多,发生大故障的可能性就越大。因此,要构建一个弹性系统,我们需要减少连接数量。如果无法做到这一点,我们需要实施“临时”切断与故障部分的连接的方法,以便错误不会级联到其他部分。


image.png

每个组件都必须假设每个其他组件都会在某个时候发生故障,并决定当这些故障发生时它会做什么。

最后,我们需要在系统中建立一些缓冲区——一些放松的方法,如果不消除对它的要求,以便有松弛来处理意外情况。

1、最小化组件间依赖

分布式系统的组件相互通信以获取数据或功能。在这两种情况下,我们都可以通过将数据/功能推送到调用组件而不是远程访问来减少连接需求。

构建大规模分布式系统迫使我们放弃标准软件工程的许多“最佳实践”。要记住的关键是,当我们采用分布式系统的复杂性来实现可扩展性时,我们还需要尽可能地控制“分布”。

1.1 重复数据

如果我们经常从另一个组件访问一些数据,我们可以在我们的组件中复制它,而不必在运行时检索它。这可以大大减少运行时依赖并帮助改善我们组件的延迟。

经常访问但有一定规律性变化的数据可以通过定期缓存刷新来临时缓存。更改频率更低或从不更改的数据(例如客户姓名)可以直接存储在我们的组件中。如果/当这些数据发生变化时,我们可能需要做一些额外的工作,但是这种增加的小开销通常是值得的,因为它可以提高弹性。

1.2 非规范化数据

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

1.3 库

为了减轻另一个组件的功能依赖性,我们可以将远程组件打包为库并将其嵌入到我们的组件中。这并不总是可能的(它可能是用其他语言编写的,或者太大而不能成为一个库)并且会带来一系列问题(功能的变化需要跨多个组件进行库升级),但是如果功能很关键并且经常被大规模访问,这是打破组件间连接并使其成为本地的可行方法。

隔离错误

错误隔离很重要,原因有两个。一是个别错误在分布式系统中更常见(许多移动部件的简单功能)。另一个是,如果我们不能防止整个系统中的联锁错误,那么我们首先就失去了构建复杂体的理由。

错误隔离的主要结构是 SLA。每个组件都声明了一些质量参数,它将在执行功能时得到尊重。这些参数可以包括延迟、错误率、并发性等。

在此 SLA 之外,调用它的组件会假定它已失败并需要自行采取适当的措施。如果组件本身检测到它无法维护其 SLA,它可以先发制人地告诉其调用者暂停并稍后再来调用。

为了保持整体系统健康,最好是快速失败而不是在违反 SLA 的情况下成功。两个组件(一个被唤起的和一个唤起的)都必须为此设置机制。

2.1 保护调用者

超时:如果被调用的组件在其 SLA 内没有响应,调用者必须超时(放弃)并改用一些回退机制(即使它抛出错误)来维护自己的 SLA 并防止一连串的 SLA 违规。

重试:由于网络不可靠,分布式系统中的许多错误只是随机的。如果调用者自己的 SLA 允许,调用者可以重试该操作。重试的前提是操作的幂等性。即它不应该改变状态或只做一次,即使它被调用了两次。

断路器:如果对组件的调用连续失败,调用者可以通过“打开电路”切断连接并停止调用一段时间。由于调用者已经有一些错误场景的备份行为,这节省了调用者宝贵的资源,这些资源本来会被浪费掉。停止调用还可以减少被调用组件的负载,并给它一些恢复的喘息空间。

断路器库具有定期轮询有问题的组件并在其性能似乎已恢复正常时重新启动调用流程的机制。

2.2 保护被调用

随机间隔:虽然重试可以减少错误,但在一个频繁使用的组件中出现一个小的性能问题可能会导致其所有调用者一次重试。这种“重试风暴”会造成负载峰值并阻止该组件恢复。为了防止这种情况,重试应该在它们之间有一个随机的时间间隔,以便交错加载。

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

3 在系统中建立缓冲区

3.1 异步通信

消息总线之类的异步通信通道允许调用远程组件,而无需非常严格的 SLA 依赖。通过让被调用组件准备好而不是立即使用消息,系统对增加的工作负载的需求变得更加灵活。

3.2 弹性配置

可扩展性最终归结为充分利用可用硬件。但是,如果看到规模增长,让系统缓口气的一个简单方法是分配更多硬件。虽然这仅在我们能够承受的成本范围内是可行的,但它为我们提供了抵御不可预测的负载变化的最后一道防线。

有关架构篇-怎么构建健壮的分布式系统?的更多相关文章

  1. ruby - Ruby 中的隐式返回值是怎么回事? - 2

    所以我开始关注ruby​​,很多东西看起来不错,但我对隐式return语句很反感。我理解默认情况下让所有内容返回self或nil但不是语句的最后一个值。对我来说,它看起来非常脆弱(尤其是)如果你正在使用一个不打算返回某些东西的方法(尤其是一个改变状态/破坏性方法的函数!),其他人可能最终依赖于一个返回对方法的目的并不重要,并且有很大的改变机会。隐式返回有什么意义?有没有办法让事情变得更简单?总是有返回以防止隐含返回被认为是好的做法吗?我是不是太担心这个了?附言当人们想要从方法中返回特定的东西时,他们是否经常使用隐式返回,这不是让你组中的其他人更容易破坏彼此的代码吗?当然,记录一切并给出

  2. ruby - 怎么来的(a_method || :other) returns :other only when assigning to a var called a_method? - 2

    给定以下方法:defsome_method:valueend以下语句按我的预期工作:some_method||:other#=>:valuex=some_method||:other#=>:value但是下面语句的行为让我感到困惑:some_method=some_method||:other#=>:other它按预期创建了一个名为some_method的局部变量,随后对some_method的调用返回该局部变量的值。但为什么它分配:other而不是:value呢?我知道这可能不是一件明智的事情,并且可以看出它可能有多么模棱两可,但我认为应该在考虑作业之前评估作业的右侧...我已经在R

  3. ruby-on-rails - 我该怎么办 :remote location validation with CarrierWave? - 2

    我在我的Rails3示例应用程序上使用CarrierWave。我想验证远程位置上传,因此当用户提交无效URL(空白或非图像)时,我不会收到标准错误异常:CarrierWave::DownloadErrorinImageController#createtryingtodownloadafilewhichisnotservedoverHTTP这是我的模型:classPaintingtrue,:length=>{:minimum=>5,:maximum=>100}validates:image,:presence=>trueend这是我的Controller:classPaintingsC

  4. ruby - 分布式事务和队列,ruby,erlang,scala - 2

    我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和

  5. ruby - 在 Ruby 中构建长字符串的简洁方法 - 2

    在编写Ruby(客户端脚本)时,我看到了三种构建更长字符串的方法,包括行尾,所有这些对我来说“闻起来”有点难看。有没有更干净、更好的方法?变量递增。ifrender_quote?quote="NowthatthereistheTec-9,acrappyspraygunfromSouthMiami."quote+="ThisgunisadvertisedasthemostpopularguninAmericancrime.Doyoubelievethatshit?"quote+="Itactuallysaysthatinthelittlebookthatcomeswithit:themo

  6. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  7. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  8. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  9. ruby - 使用 rbenv 和 ruby​​-build 构建 ruby​​ 失败,出现 undefined symbol : SSLv2_method - 2

    我正在尝试在配备ARMv7处理器的SynologyDS215j上安装ruby​​2.2.4或2.3.0。我用了optware-ng安装gcc、make、openssl、openssl-dev和zlib。我根据README中的说明安装了rbenv(版本1.0.0-19-g29b4da7)和ruby​​-build插件。.这些是随optware-ng安装的软件包及其版本binutils-2.25.1-1gcc-5.3.0-6gconv-modules-2.21-3glibc-opt-2.21-4libc-dev-2.21-1libgmp-6.0.0a-1libmpc-1.0.2-1libm

  10. ruby - EventMachine - 你怎么知道你是否落后了? - 2

    我正在研究使用EventMachine支持的twitter-streamruby​​gem来跟踪和捕获推文。我对整个事件编程有点陌生。我如何判断我在事件循环中所做的任何处理是否导致我落后?有没有简单的检查方法? 最佳答案 您可以通过使用周期性计时器并打印出耗时来确定延迟。如果您使用的是1秒的计时器,您应该已经过了大约1秒,如果它更长,您就知道您正在减慢react器的速度。@last=Time.now.to_fEM.add_periodic_timer(1)doputs"LATENCY:#{Time.now.to_f-@last}"@

随机推荐