断路器真的有效吗?重试会让情况更糟糕! - brooker


现代分布式系统被设计为允许发生系统中一部分故障,即使不能取悦所有人,也会继续为一些客户提供服务。
而断路器的设计是为了将部分故障变成完全故障。
这属于:一种机制可能会打败另一种机制。
在部署断路器之前,请确保你考虑清楚这一点:
重试(大多数情况下)会使实际分布式系统中的情况变得更糟。
  
断路器充当可能失败的操作的代理。代理应该监视最近发生的失败次数,并使用这些信息来决定是允许操作继续进行,还是直接返回异常。
来自Martin Fowler):
断路器背后的基本思想非常简单。您将受保护的函数调用包装在断路器对象中,该对象监视故障。一旦故障达到某个阈值,断路器就会跳闸,并且对断路器的所有进一步调用都会返回错误,而根本不会进行受保护的调用。

马丁富勒再次认为:
软件系统对运行在不同进程中的软件进行远程调用是很常见的,可能在网络上的不同机器上。内存调用和远程调用之间的一大区别是远程调用可能会失败,或者挂起而没有响应,直到达到某个超时限制。更糟糕的是,如果您的供应商有很多呼叫者没有响应,那么您可能会耗尽关键资源,从而导致跨多个系统的级联故障。

微软:
请注意,设置更短的超时可能有助于解决此问题,但超时不应太短以至于操作在大多数情况下都会失败,即使对服务的请求最终会成功。
 
当人们谈论断路器时,他们通常会考虑两个潜在的好处:

  • 正如 Martin 指出的那样,其中之一是尽早失败可以防止您将工作或资源浪费在注定要失败的事情上。这样做可能允许需要相同资源但不依赖于相同下游依赖项的工作继续成功。
  • 第二个好处是允许服务中的一种渐进式退化。

 
断路器的问题
断路器的问题在于它们没有考虑到真实分布式系统的基本属性。让我们考虑一个玩具分布式 NoSQL 数据库的架构:
有一个路由器层和一些分片存储noSQL数据库:当请求以 B 开头的Key进入时,它会进入 AH 分片。对以 T 开头的Key的请求转到 SZ 分片,依此类推。
实际系统往往比这更复杂和更复杂,但横向扩展数据库的顶级架构几乎总是看起来有点像这样。

这个系统怎么会失败?显然,路由器层可能无法将整个事情搞砸。但这似乎不太可能,因为它很简单,可能是无状态的,易于水平扩展等。
更有可能是其中一个存储分片过载:
假设AaronCon在城里,每个人都在尝试注册,导致AH 分片会得到很多负载,而其他分片可能会得到很少。
因为AH分片存储有很高负载,对 AH 的调用可能会开始失败,而对其他键key的调用会继续工作。

这就给断路器带来了一个问题。这个数据库坏了吗?失败是否达到了一个阈值?

如果你说是的,它坏了,那么你就使Jane和Tracy的服务变得更糟。
如果你说没有,它没有坏,那么你可能根本就没有断路器。不跳闸的断路器不是很有用。

同样的问题也适用于基于单元的架构,一个单元的故障导致断路器跳闸,可能会使整个系统看起来像瘫痪了一样,完全违背了单元的目的。基于单元的架构类似于分片的架构,只是为了可用性和爆炸半径而不是规模而分片。
 
 
我们能解决这些问题吗?
也许可以。这里的问题是,在基于单元(微服务)和分片的系统中,断路器要做正确的事情,它们需要预测一些非常具体的东西:这些参数的这个调用是否可能成功?
从其他参数的调用中推断出这一点可能是不可能的。客户对他们所调用的系统的内部工作原理根本不了解(而且主要是不应该了解),无法做出这样的决定。
通常情况下,对这个问题提出了三种解决方案。

  1. 紧密耦合:如果客户端确实知道服务中的内部数据分片是如何工作的,它就可以看到服务中的哪些分片是坏的,并做出一个好的决定。显然,这里的权衡是,这种分层违规使改变变得困难。没有人希望在不改变每个客户的情况下无法改变他们的服务。另一方面,如果你能猜得够准,这种方法可能会很有效,就像每个上游客户有断路器一样。
  2. 服务器信息:在过载时,服务可以说 "对于以A开头的请求,我已经过载了",而客户可以翻开相应的小型断路器。我见过现实世界的系统是这样工作的,但复杂性成本可能很高。
  3. 统计推理魔法/AI魔法/ML魔法:可以工作。很难做到正确。当到达的流量看起来与训练集完全不一样时,写事后总结会很有趣。