系统可靠性
当重试变成中断时
当每一层都试图自行恢复时,轻微的速度下降可能会导致整个系统的故障。遵循链条——以及可以阻止它的边界。
追踪故障链
一个意图,二十七种可能的尝试
打开全尺寸视觉 ↗解释和假设
最坏情况的说明性分支:三层,每层允许总共三次尝试,包括原始分支。没有早期成功或取消;实际流量取决于实施。环面积不是工作量的衡量标准。
第一个失败的请求通常是中断中最不有趣的部分。依赖关系花费的时间比预期的要长,一个调用者放弃等待,并且应用程序再次尝试。这看起来是一个明智的恢复行动。当成千上万的其他调用者得出相同的结论,几个层做出自己的尝试,而原始工作仍在运行时,困难就出现了。该系统现在将越来越多的容量用于从其恢复机制产生的负载中恢复。单个重试循环中的任何内容都不需要看起来明显不合理,组合行为就会变得具有破坏性。
考虑一个明确假设的订单状态服务。它通常有足够的容量来处理传入的工作,但下游依赖项会减慢两分钟。请求保持更长时间,调用者的超时开始到期,并且客户端库开始另一次尝试。外部服务也有重试策略。用户界面提供了一个重试按钮,因为它自己的截止日期已过。业务需求几乎没有变化,但依赖关系却在可以完成的工作更少的时候看到了更多的尝试工作。本文遵循该链条,然后研究设计可以中断该链条的地方。该场景和每个数字示例都是说明性的,而不是 MGCA 客户事件的说明或测量的基准。
重试是对资源的新请求,即使它代表旧的意图。这种区别是理解失败的起点。企业可能仍需要一份状态更新、一份已提交的申请或一份已完成的报告。基础设施可能正在处理多次尝试来生成它。如果仪表板对请求进行计数而不将其连接到底层操作,则额外的活动可能看起来像是客户需求激增。如果成功是根据每次尝试而不是根据有用的结果来衡量的,那么服务也会显得比等待服务的人的体验更健康。
1. 小小的延迟获得了第二次生命
超时回答了一个本地问题:调用者将等待多长时间?它本身不会回答远程操作是否已停止、在执行前失败或在未发送确认的情况下完成。这些是不同的结果。对于读来说,重复请求可能主要消耗资源。对于写入来说,不确定的结果也会产生正确性问题。接收系统可能已经提交了更改。调用者希望收到明确的答案并不会使另一次执行变得安全。因此,需要一起考虑容量保护和业务正确性,而不是将重试分配给传输层并假设应用程序的其余部分不受影响。
对速度减慢的第一反应通常是增加超时。这可以减少错误故障,即正常变化被误认为是中断。它还可以让更多请求占用资源更长时间。降低超时会产生相反的权衡:调用者会更快地释放等待状态,但可能会在之前的工作保持活动状态时发起替换尝试。这两个方向本质上都不是正确的。有用的问题是哪些资源仍然被占用,哪些工作实际上停止了,以及所选的截止日期与完成任务的价值有何关系。在不回答这些问题的情况下更改数字只能转移而不是消除压力。
还有不止一个时钟。建立连接、解析名称、获取身份令牌、等待池连接和接收响应都会消耗调用者的部分可用时间。排除较早等待的请求超时可能会给整个操作带来误导性的印象。相反,每个阶段独立慷慨的津贴可能会产生远远超出面向用户的承诺的总体延迟。设计需要对经过的时间进行连贯的描述,包括远程调用开始之前和尝试之间花费的时间。重要的边界是整个任务,而不是最容易配置的计时器。
在假设的服务中,想象可见页面在五秒后停止等待,而允许内部客户端继续等待二十秒。这些值不是推荐设置。它们暴露了所有权问题。在该期间的部分时间里,服务可能正在处理原始读者已经离开的请求。如果页面提交了新的操作,旧的工作和新的工作会竞争。如果传播取消,接收组件仍然需要定义它可以安全取消的内容。停止网络等待和撤消已提交的事务不是同一功能。截止日期可以终止结果的有用性,而不会逆转其效果。
因此,第一个中断点是有所有者的最后期限。组件应该知道它可能花费多少操作的剩余时间,是否可以在其中合理地完成另一次尝试,以及当该时间耗尽时会发生什么。答案可能是明显的失败、带有状态引用的保留异步操作或标记为不完整的结果。它不应该是将相同的工作无形地转移到另一个无限制的重试循环中。当恢复策略提高了在任务的实际约束范围内获得有效结果的机会时,它是有用的。
亚马逊的工程指南讨论了超时、退避、抖动以及超时调用已经产生副作用的风险。 Microsoft 描述了相关的重试风暴反模式:频繁尝试可能会阻止陷入困境的服务恢复。这些来源支持广泛的机制,而不是验证本文中的特定数字。接下来的设计推理将该机制应用于故意简化的服务,以便结果仍然是可检查的。 [1] [2]
2. 倍增发生在团队之间
三次尝试听起来很温和。三层,每层允许总共三次尝试,可能会有很大不同。在特定的最坏情况假设下,即每次外部尝试都会导致所有内部尝试,并且没有一个会提前成功,最深的依赖项可以针对一个原始操作接收二十七次尝试。这是跨调用层次结构的乘法,而不是声称每次失败都会自动产生该计数。取消、截止日期、早期成功、电路状态和重试的实际所有权都可以减少它。计算的重点是揭示在独立审查每个组件时很容易错过的交互。
术语在这里很重要。描述为三次重试的设置可能意味着原始尝试加上三次尝试,而三次尝试包括原始尝试。混淆这些定义会在任何实现细节进入之前改变计算。架构讨论应该写下每个边界允许的总尝试次数,并确定哪个组件启动它们。它还应该包括客户端库、网关和基础设施提供的行为。仅显示应用程序代码的图表可以省略主导流量的重试机制。向调用者隐藏故障的组件仍然会增加系统的工作负载。
组织原因是可以理解的。每个团队都希望自己的服务能够容忍短暂的中断。共享库采用标准策略。网关增加了另一层保护。工作人员将异常视为放弃并再次接收消息的原因。这些选择都不需要粗心。失败的出现是因为这些政策具有依赖性但没有共享预算。局部弹性已成为集体乐观主义:每个参与者都假设另一次尝试是可以承受的,而没有参与者对业务运营可以产生的尝试总数负责。
开口处的放射状图使这一点显而易见。它的环代表一个原始操作,然后是故意约束模型中的三个、九个和二十七个可能的下游尝试。这些环不是测量的流量,它们的面积也不是性能估计。它们是一种检查分支假设的方法,而无需绘制另一个矩形应用程序景观。向外追随一个单一的意图,并询问新作品在哪里创作。如果几个层可以独立扩展它,则仅检查最终客户端的最大重试次数将无法确定整个路由可以施加的最大负载。
一种实际的应对措施是在应用程序有足够上下文来判断操作的边界处明确重试所有权。这并不意味着每个系统都应该将所有弹性逻辑集中在一个地方。当应用程序管理较长的任务时,传输可以合理地恢复断开的连接。要求是他们的组合行为被理解并受到限制。当上层认为它已经耗尽了任务的配额后,下层不应该默默地重新启动完整的业务操作。重新连接、重新发送和重新执行之间的界限需要在设计和观察中都可见。
重试预算在每个请求的最大值之外添加了第二个限制。应用程序可以限制故障期间引入的额外工作总量。必须谨慎选择核算单位:请求、并发尝试、经过的时间、加权成本或组合。廉价的缓存读取和昂贵的报告不一定是可以互换的令牌。该机制还应该定义预算为空时会发生什么。只需将被拒绝的重试移至专用队列即可保留压力,同时将其隐藏在原始限制器之外。有用的预算会改变达到有限资源的行为。
没有普遍的百分比可以使这样的预算正确。可用的恢复能力、依赖契约、任务价值和失败行为各不相同。服务可能会为短暂的瞬时错误保留有限的容限,同时拒绝在持续故障期间增加昂贵的工作。另一种可能会将符合条件的任务推迟到单独控制的后台进程。重要的是,当系统承受压力、成功很少、以及多个调用者一起失败时,该策略仍然有意义。预算的补充取决于成功,需要对低成功时期进行明确解释,而不是意外锁定或无限的紧急优先权。
失败的尝试并不是新的业务指令
对于可写入的服务,操作的标识需要在尝试中幸存下来。如果客户端每次重新发送相同意图时都会生成新的业务标识符,则接收方可以合理地将请求视为不同的操作。如果它在不检查关联内容的情况下重用标识符,则相同的密钥可能会附加到实质上不同的更改。因此,幂等性设计需要的不仅仅是一个名为请求 ID 的字段。它需要一个范围、与预期操作的关系、保留期限以及当重复的标识符带着不兼容的内容到达时定义的响应。
结果记录同样重要。如果接收方可以识别已完成的操作但无法恢复其结果,则即使防止重复执行,调用方也可能仍不确定。该服务应决定是否可以返回先前的结果、提供状态参考或需要调查。正确的安排取决于操作及其信息处理要求。一般区别是稳定的:识别旧指令、防止另一种效果和解释现有效果是相关功能,但它们不是由同一机制自动传递的。
3. 候诊室不会创造容量
队列可以使工作负载更容易控制。它将接受与执行分开,让消费者有机会以可持续的速度处理工作。它还可能掩盖该服务无法实现的日益增长的承诺。因此,队列深度本身既不是成功指标,也不是解释。包含十个慢报告的队列可能比包含一千个小更新的队列代表更多的工作。紧急请求的短队列可能已经太旧了。有用的观察将积压与服务成本、完成能力以及结果失去其商业价值的时间联系起来。
Microsoft 基于队列的负载均衡模式描述了使用缓冲区来平滑需求,并强调了控制下游处理的需要。当向服务提供工作时,模式会发生变化;它不制造吞吐量。当将其应用于假设的经济放缓时,这个界限很重要。如果消费者只要在容量恢复时尽快清空队列,就可以重新创造最初的激增。如果自动缩放消费者增加了对仍然受限的依赖项的压力,则队列已经转移了过载而不是解决了它。从缓冲区释放的量需要其自身的操作限制。 [3]
产能可以恢复,而积压订单不断增加
打开全尺寸视觉 ↗该图表故意使用了简单的流体模型。原始需求是每秒八十个工作单位。在前 120 秒内,处理能力降至 60,然后返回到 100。每个单位都有相同的假设成本。初始积压为零,存储在此计算中没有限制作用,并且没有工作过期或永久失败。这些假设使算术变得透明。它们没有描述真实代理、数据库、网络或应用程序的完整行为。目的是展示未变化的业务工作负载如何在放大的尝试工作负载继续落后的情况下恢复。
由于没有额外的尝试流量进入该建模队列,因此在减速期间每秒会累积 20 个单位。两分钟后,积压的订单数量为两千四百。一旦容量恢复到 100,而新需求仍然是 80,该服务每秒就有 20 个单位的备用处理能力。因此,清除积压的工作还需要两分钟。从开始后 240 秒队列就空了。恢复不会在容量立即返回时发生:服务首先必须偿还它所接受的工作,同时它比它的到达速度慢。
现在在整个显示的时间间隔内添加假设的每秒 40 次尝试单位。这是一个敏感性案例,而不是对特定重试算法的预测。在减速期间,抵达人数从 120 人变为 60 人,因此到两分钟点时累计了 7,200 件。在运力恢复到 100 人之后,抵达人数仍比完成人数多 20 人。六分钟后,积压已达到一万二千,而不是耗尽。此比较中的额外需求保持不变,以隔离其影响。真正的重试数量会随着截止日期、退避、成功完成和应用程序应用的控制而变化。
这两个轨迹是根据相同的规则计算的:积压工作不能为负,并且它随着已承认的工作减去已完成的工作而变化。它们的不同结果并不能证明每次重试都是有害的。在一次短暂的独立失败之后,一次精心选择的尝试可以恢复一项有价值的任务。这些跟踪显示了为什么需要根据可用容量来评估尝试。当受限服务无法处理额外负载时,更多的持久性可以延长无人收到及时结果的时间。即使底层依赖关系仍在修复中,减少额外流量也可以成为一种恢复操作。
提供的负载和承认的负载之间的区别至关重要。限制器可能会看到大量请求,但只允许某些请求进入工作队列。被拒绝的尝试仍然会在边界处付出一些代价,因此进入路径必须足够便宜才能在压力下运行。他们不应该仅仅为了决定是否拒绝而执行同样昂贵的依赖项调用。同时,廉价拒绝并不是已完成的客户任务。该服务应分别观察需求、接纳、拒绝和有用的完成情况,以便队列增长的明显减少不会被误认为成功完成。
队列年龄添加了缺失的时间维度。假设某个特定报告仅在决策会议之前有用。即使系统最终报告成功,会议后处理它也可能没有什么价值。无限期地保留这项工作可能会延迟仍然重要的新任务。然而,到期必须是一个具有可见结果的业务决策,而不是默默地删除工作以改进图表。调用者需要知道请求是否被接受以完成、拒绝、被取代或允许过期。通过放弃合约而获得的稳定队列并不是恢复的服务。
同样的推理使优先级变得复杂。优先处理紧急任务可以保护重要成果,但持续不断的紧急工作可能会让其他一切都挨饿。租户、操作和工作负载类别之间的公平性需要明确的策略。单个全局先进先出队列可能易于操作,但隔离性较差。许多小队列可以改善分离,同时增加监控和调度的复杂性。有用的选择取决于哪些各方共享稀缺资源,以及当无法立即满足总需求时组织可以捍卫哪些承诺。
4. 建立一个超载无法借用的边界
四种不同的限额,一项服务
打开全尺寸视觉 ↗解释和假设
入场 — 廉价地接受、拒绝或推迟;保留操作标识。 · 等待工作 — 限制储存和使用年限;将直播需求与重播分开。 · 主动执行 — 限制真正的飞行中依赖工作,而不仅仅是等待呼叫者。 · 依赖性 — 尊重其能力;以受控的速度释放恢复工作。
等距视图将四个关注点分开,这些关注点通常被压缩到一个服务框中:准入、等待、主动执行和下游依赖。每一层都有不同的稀缺资源。录取途径必须快速做出决定。缓冲区具有有限的存储空间和年龄分布。 Worker 消耗内存、连接槽和执行时间。依赖项有自己的容量和权限边界。使这些层可见有助于解释为什么增加工作人员数量并不等于增加整个服务的容量。当下一层从无界共享池借用时,一层的限制可能会被打破。
以一个工作人员为例,该工作人员将自身限制为适度数量的活动作业,但允许每个作业打开许多并发下游调用。顶层限制似乎是负责任的,但重要的资源却在其之下成倍增加。当在底层操作实际停止消耗依赖项之前超时释放应用程序级许可时,也会出现相同的问题。限制器正在计算一件事,而资源正在为另一件事付费。设计应确定获得许可证的时间点、许可证涵盖的工作以及证明释放许可证合理性的观察结果。
隔离很有用,因为不相关的工作不应该因最慢的依赖而失去其所有能力。单独的池或并发限额可以防止失败的集成占用正常集成所需的资源。这不是免费的。当另一个分区繁忙时,分区可能会导致容量闲置,而分区过多会使系统难以调整。该值是更可预测的故障边界。应根据哪些工作负载可能相互影响来解释这一选择,而不是将隔板作为通用指令来为每个功能创建单独的队列和线程池。
断路器做出与重试策略不同的决定。它可以暂时停止对很可能失败的资源的尝试,并允许受控探测来评估恢复情况。微软的模式描述了这种分离。对于假设的服务,有趣的问题是断路器周围会发生什么:被阻止的尝试是否消耗重试预算,后备工作是否使用相同的失败依赖项,以及探测群体是否在所有实例中保持有限。架构图中绘制的断路器本身并不建立那些周围的行为。 [4]
后备可能会意外地使情况变得更糟。如果缓存未命中将每个请求发送到原始依赖项,则显然有弹性的缓存路径可以在命中率较低的时期集中负载。如果备用提供商的配额较低,则立即切换所有呼叫者可以转移事件。如果降级答案的计算成本很高,则它可能无法保留足够的可用容量。回退应被评估为具有自己的依赖性和正确性条件的另一个工作负载。它的名字并不能证明它在故障主路径的规模上更便宜、更安全或可用。
还有一些任务不能接受过时的信息。对于某些读者来说,返回具有可见时间戳的较旧的信息结果可能是合理的降级。使用旧的授权决策或将过时的业务状态呈现为当前状态可能会带来不同的风险。应用程序需要决定结果的哪些部分可能会降级、如何传达该条件以及必须拒绝哪些操作。通过隐藏不确定性来保持可用性可能会损害业务,即使它使错误率仪表板看起来更好。失败响应应该保留服务所知道内容的真实描述。
Google 对过载的处理讨论了降级响应,以及当即使降级响应也无法提供服务时需要拒绝工作。这为设计选择提供了有用的背景,否则听起来可能是失败主义的:拒绝某些请求可以保护完成其他请求的能力。它不会为假设的服务选择业务优先级。这些优先级需要一份明确的合同,规定哪些任务可以等待,哪些任务可能会降级,哪些任务应该很快失败。然后,技术机制需要执行该合同,而不仅仅是将其记录在文档中。 [5]
5.退避改变时序;抖动改变了公司
两个客户可能会出现各自合理的延误,但仍然会一起到达。如果大量群体同时失败并且每个成员等待相同的时间间隔,则其下一波可以保持同步。不断增长的延迟会改变波之间的间距,但不一定会分散每个波的成员。抖动会给它们的时序带来变化。重要的想法不是随机性作为公式的装饰性补充。它正在避免一种反复表现为协调爆发的恢复群体。所需的分配和范围应符合协议、截止日期和观察到的工作量。
AWS 对指数退避和抖动的讨论说明了为什么确定性重试定时会导致工作集群失效。它支持计时机制,而不是其他服务的通用参数选择。在假设的应用程序中,较长的随机延迟可能会分散尝试,但也会将某些尝试推到调用者的有用截止日期之外。较短的范围可能符合最后期限,但在减少同步方面做得太少。设计需要这两个约束。它不应该将所选的算法名称视为每个生成的延迟都适合该任务的证据。 [6]
重试循环还需要对响应进行解释。身份验证失败、被拒绝的业务规则和临时容量限制并不要求相同的行为。重复未更改的无效请求通常无法修复导致其无效的信息。限制响应可以提供计时指导,但调用者仍然需要遵守自己的截止日期和总限额。连接失败可能会导致执行不确定。分类应该导致有关操作的决定,而不仅仅是为每个异常分配其他尝试次数的查找。
尝试计划应包括完成有用工作、等待响应以及在下次尝试之前等待所花费的时间。当单独列出延迟时,策略可能看起来很短,但一旦包含执行,就会超过任务的整个时间限额。如果应用程序允许的话,它还应该考虑重叠尝试。并行推测有时可以减少延迟,但会花费额外的容量来实现这一点。在过载情况下,这种权衡可能会特别昂贵。组织应该知道服务何时做出选择、哪些操作符合资格以及什么会停止额外的工作。
时间表并不是同步的唯一来源。定期轮询、令牌刷新、计划批处理启动以及共享事件后重新启动的一组进程都可以集中活动。因此,即使显式重试循环得到改进,假设的中断也可能会再次发生。有用的调查询问哪些时钟在工作负载中共享,以及它们触发了哪些昂贵的操作。分散后台工作可能会有所帮助,但截止日期、工作时间和外部服务窗口可能会限制这种自由。正确的解决方案会改变相关的到达模式,同时保留计划任务的目的。
再次按下按钮的呼叫者
人们可以成为另一个重试层。屏幕旋转而不解释请求是否被接受会导致重新提交。禁用的按钮可以减少意外重复,但无法在丢失响应后确定服务器的状态。持久的操作引用和清晰的状态视图可以为读者提供一种检查第一个请求而不是创建另一个请求的方法。接口应该区分等待接受的操作和请求服务器启动新的操作。当较短的交互截止日期位于较长的异步流程之前时,这种区别变得尤其有价值。
措辞很重要,因为软件正在做出承诺。当结果未知时说请求失败会鼓励采取与说处理已接受且正在检查其状态不同的操作。这两条消息都不应声称系统不具备确定性。状态视图还必须避免在事件期间成为不受控制的轮询源。选择间隔、在适当的情况下缓存状态以及限制重复刷新是同一容量设计的一部分。有用的用户体验可以帮助人们恢复他们的意图,而不会将他们的不确定性变成另一波工作。
6. 恢复交通需要一条回家路线
队列驱动的服务引入了另一个区别:成功处理和成功确认是单独的事件。业务效果可能会在代理得知消息可以被删除之前完成。如果确认丢失,系统可以再次看到该消息。 Azure 服务总线将传输、锁定和结算记录为交付的单独部分。架构结果是将消息的处理身份与业务操作连接起来,并有意地处理重复传递。增加交付计数阈值并不能证明重复执行是安全的。 [7]
工人还需要认识到重复工作不会变得有效。格式错误的有效负载、不受支持的操作版本或永久缺失的先决条件可能需要纠正或调查。将其无限期地返回到活动队列可能会消耗容量,而不会提高成功的机会。单独的故障路径可以使普通工作负载继续运行,但该路径需要一个所有者和可见的后果。其内容从不被检查的死信队列不是恢复策略;它是未解决的承诺的累积,而这些承诺恰好位于主要吞吐量图表之外。
Microsoft 的服务总线死信文档解释了辅助子队列的作用以及应用程序处理其消息的需要。假设服务的重要选择是重新提交之前发生的情况。团队需要确定潜在的缺陷是否已得到纠正,最初的业务意图是否仍然存在,以及早期的尝试是否已经产生了效果。跳过这些问题的批量重播可以将旧的不确定性转化为新的负载。恢复过程应该能够选择工作,而不是将每个存储的消息视为同样安全地再次运行。 [8]
当容量恢复时,实时需求和历史恢复工作会相互竞争。以最大可用速率耗尽所有旧任务的服务可能会给当前用户带来第二次中断。为新工作保留一切的服务可能永远不会清除积压的工作。因此,恢复工作的释放率需要与实际闲置产能和业务优先级挂钩的政策。它可以随着依赖项行为的变化而调整。要点是,恢复本身就是一种工作负载,而不是不受普通流量限制的特殊类别。
恢复决策也有一个停止条件。当延迟增加、最旧的实时请求超过其有效期限、重复检测发现意外模式或依赖项再次开始限制时,重播可能需要暂停。相关信号取决于服务。重要的是,释放旧工作的人或机制可以观察它们并在缓冲区成为另一次激增的来源之前采取行动。没有反馈路径的重放命令可以提供持久性,但对其后果几乎无法控制。
保留出处可以使反馈更有用。操作的首次提交、后续尝试、队列交付和任何手动更正都应该是可追踪的。标识符不需要公开敏感内容即可实现此目的。他们确实需要与业务意图的稳定关系以及足够的配置上下文来解释每次尝试运行的策略。如果没有这种连续性,团队就可以通过事件响应来决定是否要考虑一项顽固的操作、许多新请求或两者的混合。额外的日志量会成为另一种资源压力,而无法提供所需的理解。
7. 寻找正在完成的工作
一项服务可能很忙、可用但同时没有帮助。 CPU、连接利用率和请求率描述活动,而有用的完成描述进度。任何一个类别都不能取代另一个类别。有趣的观察是它们如何分歧。如果尝试次数增加,而完成的唯一操作次数减少,则服务可能会花费更多,但效果却更少。如果延迟改善只是因为大部分工作被拒绝,那么应用程序可能已经保护了其资源,但仍然让许多读者失败。操作视图应该使这两个事实都可见,而不是选择哪个指标讲述更令人放心的故事。
分母对于重试的应用程序尤其重要。每次操作的尝试次数可能会扭曲尝试级别的成功率。用户级完成率可以隐藏每次成功所需的资源消耗量。有用的仪表板将原始需求、总尝试、已承认的工作、唯一已完成的操作、有效完成的时间和仍不确定的结果分开。它还显示了年龄和工作负荷等级,这些区别会影响决策。这些措施描述了不同的问题;仅当团队可以将其中的更改与操作联系起来时,收集它们才有用。
一种诊断比率是每个原始操作尝试的依赖性调用。另一个是在启动截止日期已过的操作上花费的稀缺执行时间的份额。两者都不应被视为普遍服务水平目标。它们是使可疑机制可见的方法。该定义需要足够精确,以避免将无害的连接恢复视为额外的业务执行或将有意的异步任务视为废弃的工作。该衡量标准在区分解释时才有价值,而不是在事件仪表板上提供另一条有吸引力的线时。
成本是该观察的一部分。额外的尝试可能会增加请求费用、日志记录量、连接流失或计算消耗,即使它们没有完成更多业务工作。如果分母随着重试而增加,则每个请求的成本似乎会提高,而每个有用结果的成本则会恶化。组织需要一个成本视图来跟踪任务,以包括失败的和重复的工作。这里没有要求节省。关键是选择一个会计边界,该边界将揭示拟议的弹性变更是否减少浪费,而不是简单地将其转移到其他服务或发票。
测试链条,而不仅仅是循环
单元测试中的成功重试几乎无法确定相关故障下的机群。更具揭示性的受控测试可以改变依赖项的延迟或完成能力,同时保持原始需求的可识别性。它观察尝试放大是否仍然有限,最后期限是否按预期结束工作以及有用的吞吐量是否在干扰后恢复。环境和限制应该使测试安全。目的不是为了展示韧性而造成失控的生产事故。它是为了挑战这样一个特定的假设:当许多调用者一起失败时,额外的尝试仍然有帮助。
这些场景应该区分缓慢的反应、明确的拒绝和未知的结果。它们应该包括一种在调用者截止日期之前恢复的依赖项和一种不能恢复的依赖项。基于队列的服务还应该执行确认不确定性、过期工作和故意限制重播。预期的结果不仅仅是每一个提交的任务最终都成功。有些任务应该拒绝或停止尝试。有意义的问题是观察到的结果是否与合同相符,同时有限的资源仍然受到保护,并且组织可以解释每类工作发生了什么。
比较需要相同的原始需求。否则,看起来恢复得更快的配置可能只是接收到了更轻松的工作负载。帐户应保留与结果相关的到达模式、任务组合、依赖条件和配置版本。它应该将计算示例(例如本文中的图表)与运行测试中获得的测量结果区分开来。重复一个看似合理的数字故事,就好像它是一个基准一样,会削弱证据。透明的小模型很有用,因为它的假设可以受到挑战,而不是因为它的平滑线条类似于生产轨迹。
8、允许停止尝试的系统
返回第一个延迟的请求。它不一定会成为停电。故障链通过几次转变获得了力量:超时被解释为允许开始更多工作;在没有共同预算的情况下,层层增加尝试;等待工作掩盖了不断扩大的能力差距;恢复流量被视为其紧迫性会产生额外的吞吐量。每个转变都提供了一个中断链条的地方。该设计不需要每种可能的弹性模式。它需要在该服务可能将不确定性转化为额外负载或重复业务影响的点上进行控制。
在某些情况下,另一次尝试正是正确的反应。短暂的独立故障可能会消失,安全读取可能会在另一条路线上快速完成,或者有限的重新提交可能会恢复有价值的任务。这个论点并不反对坚持。坚持就是对时间、能力和行动的意义负责。服务应该能够解释为什么进行尝试、为此支付了哪些津贴以及哪些观察结果会阻止下一次尝试。该解释比为可靠性而启用重试的一般声明更有用。
因此,最重要的恢复能力可能是停止能力。当截止日期使结果变得毫无用处时,停止尝试。停止从不相关的工作中借用能力。当实时服务开始受到影响时,停止重放积压的工作。当执行结果仍未知时,停止声称确定失败。这些是不同的决定,每个决定都需要适当的所有者或机制。它们共同为恢复潜在的依赖关系和组织纠正剩余的工作腾出了空间,而不是强迫每个未解决的意图通过相同的超载路径返回。
可靠的服务并不承诺永远尝试。它做出了更实际的承诺:接受的工作有明确的意义,额外的努力有限制,失败会产生人们可以理解并采取行动的结果。当这些条件成立时,重试仍然是一种有用的工具,而不是成为没有负责任的客户的无形工作负载。这种差异在恢复期间表现得最为明显。当原始故障消退时,系统对其自身行为有足够的控制力,可以恢复有用的工作。
