当连接到远程服务或资源到时候,处理那些需要一段时间才能修复的系统缺陷。这能优化应用对稳定性和可靠性。
上下文和问题在分布式环境中,对远端服务或资源的请求可能会由于诸如以下临时性错误而失败:缓慢的网络请求,连接超时,资源被过度使用,或服务临时不可用。通常情况下,这些错误能够在短暂的中断后自我修复。一个健壮的云端应用应该能够通过重试模式等策略来处理这些问题。
然而,有的时候这些错误缘于一些未知的事件,从而需要更长的时间修复。这些错误可能是系统一部分无法连接,或是整个服务都响应失败。在这些情况下,盲目的去重试之前的操作可能并没有意义,而且也不太可能会成功,取而代之系统应该快速识别出操作失败然后去处理这些失败。
另外,如果一个服务非常繁忙,系统中的一部分出错将导致级连的错误。例如,一个调用其他服务的操作可以设定一个超时,然后在超时后返回错误。然而,这个策略可能导致很多访问这个服务的并发请求阻塞,直到超时。这些阻塞的请求可能占用了重要的系统资源,诸如内存,线程,数据库链接等。因此可能导致这些资源被耗尽,进而导致其他不相干的模块因为资源竞争而失败。在这些情况下,直接让这些操作失败,然后在合适的时候再去尝试调用这些服务,似乎是更合理的选择。设定一个短一些的超时时长可能会有助于解决这个问题,但是又不能设定的太短而中断那些最终可能成功的请求。
解决方案由 Michael Nygard 在其[书中](https://pragprog.com/book/mnee/release-it)普及的断路器模式,能够阻止应用重复的尝试执行可能失败的请求。这允许系统继续运行,而不用等待那些错误被修复,也不用浪费 CPU 循环,因为它已经识别到该错误是持续性的。断路器模式也使系统能够检测出错误是否已被修复。如果问题已经被修复,系统能够重新调用该操作。
断路器模式的目的和重试模式有所不同。重试模式使应用能够重试期望成功的操作。断路器模式阻止应用去调用很可能失败的操作。应用可以联合使用两种模式。然而,重试逻辑应该能够处理断路器模式抛出的异常,并在断路器指示该错误非短期可修复的错误时,停止重试。
断路器为可能会失败的操作充当代理的角色。这个代理监视最近发生的失败的数量,然后用这些信息判断是否继续执行该操作,还是直接返回异常。
该代理可以通过一个状态机来实现,该状态机应模拟电子断路器来实现以下状态:
关闭:来自应用的请求直接路由到对应的操作。代理维护一个计数器来记录最近失败的次数。如果一个操作失败,该计数器加一。如果最近失败的次数在指定时间段内超过一个阈值,代理被设定到 开启 状态。同时,代理启动一个计时器,当计时器超时后,代理被设定到 半开状态。
设定计时器的目的是在应用重试该操作前,给系统留出时间修复导致该错误的问题。
开启:从应用发送给该服务的请求直接失败,并返回异常。
半开:允许少量的请求通过代理调用该操作。如果请求成功,系统假定之前引起操作失败的错误已被修复,断路器设定到 关闭状态(且将失败计数器重置)。如果任何请求失败,断路器便假定之前的错误依旧存在,然后把状态重新置为打开,重启超时计时器,并为系统恢复该错误设定更长的恢复时间。
半开 状态有助于使恢复中的系统避免遭受突发的大量请求。在服务恢复过程中,它可能只能支撑有限数量的请求,直至恢复完全完成。在恢复过程中接收大量请求,可能会使服务超时,甚至再次失败。
在上图中,关闭状态下使用的计数器是基于时间的,它会自动定期重置。这能够使断路器避免因偶发性失败而切换到失败状态。失败阈值设定使断路器只有在指定的时间内失败的次数达到了指定值后才切换到失败状态。半开状态下使用的计数器用来记录请求成功的次数。当连续成功的请求数量超过一个指定值后,断路器将切换到 关闭状态。如果任一调用失败,断路器将直接进入打开状态,下次进入半开状态的时候,成功计数器将被清零。
系统如何修复是属于本模式以外的内容,可能通过重新加载数据,重启失败的组件,或是修复网络问题。