• 发文
  • 评论
  • 微博
  • 空间
  • 微信

博文精译-断路器模式

java达人 2019-04-26 10:54 发文


软件系统对运行在不同进程或者网路中不同的机器的软件进行远程调用是很常见的。内存中调用和远程调用之间的一个主要区别是,远程调用可能会失败,或者在达到某个超时限制之前挂起而没有响应。更糟糕的是,如果一个响应延迟的服务提供方上有许多调用者,那么您可能会耗尽关键资源,导致跨多个系统的连锁故障。Michael Nygard在他的优秀著作《发布》中推广了断路器模式,以防止这种灾难性的连锁故障。

断路器的基本原理很简单。您将一个受保护的函数调用封装在一个断路器对象中,该断路器对象监视故障。一旦故障达到某个阈值,断路器就会跳闸,所有对断路器的继续调用都会返回一个错误,受保护的调用也不会继续。如果断路器跳闸,您通常还需要通过监视器进行警报。

下面是Ruby写的一个简单示例,用于防止超时。

我使用block (Lambda)设置了断路器,它是受保护的调用。

cb = CircuitBreaker.new {|arg| @supplier.func arg}

断路器存储block,初始化各种参数(阈值、超时和监视功能),并将断路器重置为关闭状态。

class CircuitBreaker...


  attr_accessor :invocation_timeout, :failure_threshold, :monitor


  def initialize &block


    @circuit = block


    @invocation_timeout = 0.01


    @failure_threshold = 5


    @monitor = acquire_monitor


    reset


  end

如果线路关闭,则调用断路器将调用底层block,如果打开则返回错误

# client code


    aCircuitBreaker.call(5)


class CircuitBreaker...


  def call args


    case state


    when :closed


      begin


        do_call args


      rescue Timeout::Error


        record_failure


        raise $!


      end


    when :open then raise CircuitBreaker::Open


    else raise "Unreachable Code"


    end


  end


  def do_call args


    result = Timeout::timeout(@invocation_timeout) do


      @circuit.call args


    end


    reset


    return result


  end

如果我们调用超时,我们故障计数器计数增加,调用成功则将其重置为零。

class CircuitBreaker...


  def record_failure


    @failure_count += 1


    @monitor.alert(:open_circuit) if :open == state


  end


  def reset


    @failure_count = 0


    @monitor.alert :reset_circuit


  end

将故障失败数与阈值进行比较,确定断路器的状态

class CircuitBreaker...


  def state


     (@failure_count >= @failure_threshold) ? :open : :closed


  end

这个简单的断路器避免了在电路打开时进行调用,但是当一切恢复正常时需要外部干预来重置它。对于建筑物中的断路器,这是一种合理的方法,但是对于软件中断路器,我们可以让断路器本身检测底层调用是否可以继续。我们可以通过在适当的间隔之后再次尝试被保护调用来实现这种自重置行为,成功时则重置断路器。

创建这种断路器意味着需要为重置尝试添加一个阈值,并设置一个变量来保存上次错误时间。

class ResetCircuitBreaker...


  def initialize &block


    @circuit = block


    @invocation_timeout = 0.01


    @failure_threshold = 5


    @monitor = BreakerMonitor.new


    @reset_timeout = 0.1


    reset


  end


  def reset


    @failure_count = 0


    @last_failure_time = nil


    @monitor.alert :reset_circuit


  end

现在出现了第三种状态—半开放状态—这意味着线路已经准备好进行试验性的真实调用,看看问题是否已经修复。

class ResetCircuitBreaker...


  def state


    case


    when (@failure_count >= @failure_threshold) &&


        (Time.now - @last_failure_time) > @reset_timeout


      :half_open


    when (@failure_count >= @failure_threshold)


      :open


    else


      :closed


    end


  end

在半打开状态下的试验性调用,如果成功,将重置断路器;如果失败,将重启超时设置。

class ResetCircuitBreaker...


  def call args


    case state


    when :closed, :half_open


      begin


        do_call args


      rescue Timeout::Error


        record_failure


        raise $!


      end


    when :open


      raise CircuitBreaker::Open


    else


      raise "Unreachable"


    end


  end


  def record_failure


    @failure_count += 1


    @last_failure_time = Time.now


    @monitor.alert(:open_circuit) if :open == state


  end

这个例子很简单,在实践中断路器提供了更多的特性和参数化设置。它们通常会防止受保护调用可能引发的一系列错误,比如网络连接失败。并不是所有的错误都应该跳闸,有些是反映正常的故障,需要作为常规逻辑的一部分进行处理。

由于流量很大,您可能会遇到大量调用等待超时的问题。由于远程调用通常很慢,所以最好将每个调用放在不同的线程上,使用future或promise来处理返回的结果。从线程池中提取这些线程,在线程池耗尽时安排线路断开。

这个例子展示了一种简单的方法来跳闸—在成功调用时重置计数。一种更复杂的方法可能是查看错误的频率,比如,一旦达到50%的失败率,就会跳闸。您还可以为不同的错误设置不同的阈值,例如超时阈值为10,连接失败阈值为3。

我所展示的示例是用于同步调用的断路器,但是断路器对于异步通信也很有用。这里的一种常见技术是将所有请求放在一个队列中,服务提供者以一定速度消费该队列—这是一种避免服务器过载的有用技术。在这种情况下,当队列被填满时,线路就会断开。

就其本身而言,断路器有助于减少在可能失败的操作中占用资源。您可以避免客户端的超时等待,而断开的线路也可以避免给处于困境的服务器增加负载。我在这里讨论的是远程调用,这是使用断路器的常见情况,但是它们可以用于任何需要保护系统部件免受其他部件故障影响的情况。

断路器是一个有价值的监测点。断路器中状态的任何更改都应该被记录,断路器应该显示其状态的详细信息,以便进行更深入的监控。断路器的行为通常是一个很好的来源,来警告环境中更深层次的问题。操作人员应该能够跳闸或复位断路器。

断路器本身是有价值的,但使用断路器的客户端需要对断路器故障做出反应。与任何远程调用一样,您需要考虑在发生故障时应该做什么。它是否会使你正在进行的操作失败,或者是否还有其他的解决办法?比如信用卡授权可以放在队列中稍后处理;通过显示一些可以接受的旧数据来缓解无法获取某些数据的问题。


致谢

Pavel Shpak在示例代码中发现报告了一个bug





声明:本文为OFweek维科号作者发布,不代表OFweek维科号立场。如有侵权或其他问题,请及时联系我们举报。
2
评论

评论

    相关阅读

    暂无数据

    java达人

    我们的使命是为程序员赋能,请搜索...

    举报文章问题

    ×
    • 营销广告
    • 重复、旧闻
    • 格式问题
    • 低俗
    • 标题夸张
    • 与事实不符
    • 疑似抄袭
    • 我有话要说
    确定 取消

    举报评论问题

    ×
    • 淫秽色情
    • 营销广告
    • 恶意攻击谩骂
    • 我要吐槽
    确定 取消

    用户登录×

    请输入用户名/手机/邮箱

    请输入密码