本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
断路器模式
意图
断路器模式可以防止调用方服务在调用先前导致反复超时或失败时重试调用其他服务(被调用方)。该模式还用于检测被调用方服务何时恢复运行。
动机
多个微服务协作处理请求时,一个或多个服务可能会变得不可用或出现高延迟。复杂的应用程序使用微服务时,一个微服务中断可能会导致应用程序故障。微服务通过远程程序调用进行通信,网络连接中可能会出现瞬态错误,从而导致故障。(暂时性错误可以通过使用回退重试模式来处理。) 在同步执行期间,超时或故障的级联效应可能会导致用户体验不佳。
但是,在某些情况下,故障可能需要更长的时间才能解决,例如,当被调用方服务关闭或数据库争用导致超时时。在这种情况下,如果调用服务反复重试调用,则这些重试可能会导致网络争用和数据库线程池消耗。此外,如果多个用户反复重试应用程序,则会使问题变得更糟,且可能导致整个应用程序的性能下降。
Michael Nygard 在他的《Release It》(Nygard 2018)一书中推广了断路器模式。此设计模式可以防止调用方服务重试先前导致反复超时或失败的服务调用。它还可以检测被调用方服务何时恢复运行。
断路器对象的工作原理类似于电路断路器,当电路出现异常时,断路器会自动切断电流。电路出现故障时,电路断路器会切断电流,也就是跳闸。同样,断路器对象位于调用方和被调用方服务之间,如果被调用方不可用,则断路器会跳闸。
分布式计算的谬误
在网络中断期间,应用程序可能会无限期地等待回复并持续消耗应用程序资源。网络可用时未能重试这些操作也可能导致应用程序性能下降。如果对数据库或外部服务的 API 调用由于网络问题而超时,则在没有断路器的情况下重复调用可能会影响成本和性能。
适用性
在以下情况下使用此模式:
-
调用方服务发起了一个极有可能失败的调用。
-
被调用方服务表现出的高延迟(例如,当数据库连接速度较慢时)会导致调用方服务超时。
-
调用方服务发起同步调用,但被调用方服务不可用或延迟很长。
问题和注意事项
-
与服务无关的实现:为防止代码膨胀,我们建议您以与微服务无关且由 API 驱动的方式实现断路器对象。
-
由被调用方关闭电路:当被调用方从性能问题或故障中恢复时,他们可以将电路状态更新为
CLOSED。这是断路器模式的扩展,如果您的恢复时间目标(RTO)需要,则可以实施。 -
多线程调用:到期超时值定义为在再次路由调用以检查服务可用性之前,电路保持跳闸的时间段。在多个线程中调用被调用者服务时,第一个失败的调用将定义到期超时值。您的实现应确保后续调用不会无休止地更改到期超时时间。
-
强制打开或关闭电路:系统管理员应该能够打开或关闭电路。这可以通过更新数据库表中的到期超时值来完成。
-
可观测性:应用程序应设置日志记录,以识别断路器打开时失败的调用。
实现
高级架构
在以下示例中,调用方是订单服务,被调用方是付款服务。
如果没有故障,订单服务会通过断路器将所有调用路由到付款服务,如下图所示。
如果付款服务超时,断路器可以检测到超时并跟踪故障。
如果超时超过指定的阈值,则应用程序将打开电路。当电路开启时,断路器对象不会将调用路由到付款服务。当订单服务调用付款服务时,会立即返回失败结果。
断路器对象会定期尝试查看对付款服务的调用是否成功。
成功调用付款服务后,电路将关闭,所有后续调用将再次路由到付款服务。
使用 AWS 服务来实施
示例解决方案使用 AWS Step Functions
该解决方案还使用 Amazon DynamoDB
当服务想要调用其他服务时,它会使用被调用服务的名称启动工作流程。该工作流程从 DynamoDB CircuitStatus 表中获取断路器状态,该表存储了当前已降级的服务。如果 CircuitStatus 包含被调用方的未过期记录,则电路处于打开状态。Step Functions 工作流程立即返回失败并以 FAIL 状态退出。
如果 CircuitStatus 表不包含被调用方的记录或包含过期的记录,则表示服务正常运行。状态机定义中的 ExecuteLambda 步骤调用通过参数值发送的 Lambda 函数。如果调用成功,Step Functions 工作流程将以 SUCCESS 状态退出。
如果服务调用失败或出现超时,应用程序将在规定的次数内以指数回退方式重试。如果重试后服务调用失败,则工作流程会在 CircuitStatus 表中为该服务插入一条记录,值为 ExpiryTimeStamp,且工作流程以 FAIL 状态退出。只要断路器处于打开状态,随后对同一服务的调用就会立即返回故障结果。状态机定义中的 Get Circuit Status 步骤根据 ExpiryTimeStamp 值检查服务可用性。使用 DynamoDB 生存时间(TTL)功能将过期项目从 CircuitStatus 表中删除。
代码示例
以下代码使用 GetCircuitStatus Lambda 函数检查断路器状态。
var serviceDetails = _dbContext.QueryAsync<CircuitBreaker>(serviceName, QueryOperator.GreaterThan, new List<object> {currentTimeStamp}).GetRemainingAsync(); if (serviceDetails.Result.Count > 0) { functionData.CircuitStatus = serviceDetails.Result[0].CircuitStatus; } else { functionData.CircuitStatus = ""; }
以下代码显示了 Step Functions 工作流程中的 Amazon States Language 语句。
"Is Circuit Closed": { "Type": "Choice", "Choices": [ { "Variable": "$.CircuitStatus", "StringEquals": "OPEN", "Next": "Circuit Open" }, { "Variable": "$.CircuitStatus", "StringEquals": "", "Next": "Execute Lambda" } ] }, "Circuit Open": { "Type": "Fail" }
GitHub 存储库
有关此模式示例架构的完整实施,请参阅 GitHub 存储库 https://github.com/aws-samples/circuit-breaker-netcore-blog