服务化架构-服务的熔断

什么是熔断

软件行业中的很多技术都是借鉴于工业的,熔断也不例外。熔断机制类似于工厂的保险丝/盒,当电流过大时,为了保护电器不受损,将电路熔断或者跳闸,断电后人工更换保险丝或者合闸。当然,在软件系统里面的熔断机制,其实可以实现自动熔断和恢复。

为什么要熔断

有人可能要问了,软件系统如果健壮性做得很好,需要熔断吗?
前面我们说过,熔断是针对下游的系统来说的。举个栗子,如果下游C系统出问题了,服务100%超时,这时候你可以先不去请求C了,直到C恢复再去请求。有人说我去请求了也没关系吧,反正请求和不请求都是失败,我干嘛要把事情搞复杂了?我又不知道C什么时候能恢复,权当去测试C的可用性了呗。

嗯,你说得有道理,不过需要每个请求都要去测试吗?当然没必要,而且很多时候下游系统的超时有可能会拖垮你的系统。纳尼?这又是为啥?

我们继续举例子,如果你的系统与C系统是通过同步的方式交互,那么意味着在C返回结果前,你的系统的工作线程是要阻塞在那里的,这也就会导致你的系统吞吐量的降低,毕竟线程是不能无限创建的。

熔断的实现

原理

熔断的实现思路很简单,简而言之就是失败率统计,如果它在一段时间内侦测到许多类似的错误,会强迫其以后的多个调用快速失败,不再访问远程服务器,从而防止应用程序不断地尝试执行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的超时产生。熔断器也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。

实现要点

熔断器工作过程中,有3个状态:闭合、打开、半开。

  • 闭合(Closed)状态:我们需要一个调用失败的计数器,如果调用失败,则使失败次数加 1。如果最近失败次数超过了在给定时间内允许失败的阈值,则切换到断开 (Open) 状态。此时开启了一个超时时钟,当该时钟超过了该时间,则切换到半断开(Half-Open)状态。该超时时间的设定是给了系统一次机会来修正导致调用失败的错误,以回到正常工作的状态。在 Closed 状态下,错误计数器是基于时间的。在特定的时间间隔内会自动重置。这能够防止由于某次的偶然错误导致熔断器进入断开状态。也可以基于连续失败的次数。

  • 打开 (Open) 状态:在该状态下,对应用程序的请求会立即返回错误响应,而不调用后端的服务。这样也许比较粗暴,有些时候,我们可以 cache 住上次成功请求,直接返回缓存(当然,这个缓存放在本地内存就好了),如果没有缓存再返回错误(缓存的机制最好用在全站一样的数据,而不是用在不同的用户间不同的数据,因为后者需要缓存的数据有可能会很多)。

  • 半开(Half-Open)状态:允许应用程序一定数量的请求去调用服务。如果这些请求对服务的调用成功,那么可以认为之前导致调用失败的错误已经修正,此时熔断器切换到闭合状态 (并且将错误计数器重置)。

如果这一定数量的请求有调用失败的情况,则认为导致之前调用失败的问题仍然存在,熔断器切回到断开状态,然后重置计时器来给系统一定的时间来修正错误。半断开状态能够有效防止正在恢复中的服务被突然而来的大量请求再次拖垮。

Hystrix

在业界,一说到熔断器,很多人很容易就会联想到Netflix开源的Hystrix。是的Hyxtrix是一个非常优秀的保证系统高可用的组件,其中的熔断、舱壁(bulkhead)实现都能够帮助我们提高系统的可用性。

熔断的其他注意事项

在实现熔断器模式的时候,以下这些因素需可能需要考虑。

  • 避免侵入代码:熔断器应该作为一种比较基础的服务组件存在于系统中,尽量避免对业务代码的侵入,从而提高系统的可维护性。所以通用、可配置化是熔断组件的基本要求。

  • 状态监控:熔断器处于打开、半开状态时,应该能够在第一时间通知维护人员,让相关人员及时获知熔断器的状态,并根据其他信息决定下一步采取的措施。

  • 日志记录:熔断器应该能够记录所有失败的请求,以及一些可能会尝试成功的请求,使得管理员能够监控使用熔断器保护的服务的执行情况。

  • 性能考虑:熔断器的实现不应该阻塞并发的请求或者增加每次请求调用的负担。尤其是其中的对调用结果的统计,一般来说会成为一个共享的数据结构,这个会导致有锁的情况。在这种情况下,最好使用一些无锁的数据结构,或是 atomic 的原子操作。这样会带来更好的性能。

  • 错误区分。要能够区分出正常的调用失败和熔断失败,这样能够针对不同的失败制定不同的策略。比如对于正常的调用失败,可以采取重试机制;而对于熔断失败,那么快速失败即可。

  • 服务测试。在断开状态下,熔断器可以采用定期地 ping 一下远程的服务的健康检查接口,来判断服务是否恢复,而不是使用计时器来自动切换到半开状态。这样做的一个好处是,在服务恢复的情况下,不需要真实的用户流量就可以把状态从半开状态切回关闭状态。否则在半开状态下,即便服务已恢复了,也需要用户真实的请求来恢复,这会影响用户的真实请求。

  • 手动重置:在系统中对于失败操作的恢复时间是很难确定的,提供一个手动重置功能能够使得管理员可以手动地强制将熔断器切换到闭合状态。同样的,如果受熔断器保护的服务暂时不可用的话,管理员能够强制将熔断器设置为断开状态。

  • 熔断隔离:对于不同的服务,要设置不同的熔断器,使他们相互隔离,避免互相影响。