背景和问题

在分布式环境中,如在云。当中,应用程序运行訪问远程资源服务的操作,有可能这些操作的失败是因为瞬时故障。如的网络连接超时。或者过度使用的资源或临时不可用。这些故障一般之后短时间内纠正自己,一个强大的云应用应该准备使用的策略来处理它们。比如,通过重试模式进行说明。

可是,能够是当中的故障是因为那些不easy预见的突发事件的情况下,这可能须要更长的时间来纠正这些故障连接部分损失到服务完整的故障范围的严重程度

在这样的情况下。它可能是毫无意义的应用,不断重试运行的操作不太可能成功。而不是应用程序应该非常快接受该操作已失败,并对应地处理这个故障

此外。假设一个服务是很繁忙的在系统中的一个部分出现问题可能会导致连锁故障

比如调用一个服务的操作可被配置成实现一个超时,假设该服务无法在这段时间内响应一个失败消息答复。然而,这一策略可能导致很多并发请求同样的操作,直到超时时段期满被阻止。

这些被禁止的请求可能会持有关键系统资源。如内存线程,数据库连接等。因此。这些资源可能会耗尽从而导致该系统的其它可能无关的部件,须要使用同样的资源的失败。这些情况下,这将是优选的操作马上失败而且仅仅尝试调用服务。假设它可能成功。

注意,设置一个较短的超时可能有助于解决此问题,在超时不应该如此之短,以致操作失败的大部分时间即使该请求到服务终于会成功。


解决方式

断路器图案能够防止一个应用程序多次试图运行一个操作,即非常可能失败。同意它继续而不等待故障恢复或者浪费CPU周期,而它确定该故障是持久的断路器模式也使应用程序能够检測故障是否已经解决。假设问题似乎已经得到纠正​​,应用程序能够尝试调用操作。

注意:

断路器图案的目的重试模式的不同重试模式使应用程序能够重试操作以期望会成功

断路器图案防止应用程序运行一个操作,即非常可能失败一个应用程序能够通过使用重试模式,通过一个断路器调用操作结合这两种模式。然而重试逻辑应该是由断路器返回不论什么异常敏感放弃重试次数,假设断路器指示故障不是瞬时的。

断路器充当可能失败操作的代理。

代理应监測近期发生的故障数量,然后使用这个信息来决定是否同意该操作继续进行简单地马上返回一个异常。

代理能够被实现为状态机模拟的电路断路器的功能例如以下状态:
•关闭从应用程序请求是通过对操作进行路由。代理保持近期的失败次数的计数,而且假设该呼叫到操作不成功,则代理递增该计数假设近期的失败次数超过了一个给定时间周期内的规定的阈值时,该代理将被置于打开状态在这一点上代理启动一个超时定时器,当该定时器期满代理放置到开放状态

注意:

超时定时器的目的是为了给系统时间,纠正同意应用程序尝试再次运行该操作之前导致失败的问题

•打开从应用程序请求马上失败异常返回给应用程序
半开放从应用程序请求数量有限同意通过并调用执行假设这些请求是成功的则假定先前导致故障的故障已修复断路器切换到闭合状态故障计数器被复位假设不论什么请求失败断路器假设故障仍然存在。因此恢复到打开状态,并又一次启动超时定时器,系统的时间再延长。从故障中恢复

注意:

半开的状态是非常实用的,以防止恢复服务,突然被淹没的请求。作为服务恢复。也可能是可以支持请求的限制音量。直到恢复完毕,但在恢复过程中,海量的工作可能会导致服务超时再次失败。

图1示出了用于一个可能的实现的电路断路器的状态。

云计算设计模式(二)——断路器模式

图1  - 断路器状态

须要注意的是在图1中所用的封闭状态下的失败计数器基于时间的

定期自己主动复位这有助于防止断路器进入打开状态,假设它经受偶然的失败;使断路器跳闸进入打开状态故障阈值时,故障指定数量的指定的时间间隔期间发生的达到所使用的状态下的成功计数器记录成功尝试调用的操作的数量。断路器恢复到封闭状态连续操作调用中指定数量的已成功

假设不论什么调用失败时。断路器马上进入打开状态。而且成功的计数器其进入状态下一次复位。

Note

怎样系统恢复从外部处理,可能通过恢复又一次启动故障部件或修理的网络连接。

运行断路器图案添加了稳定性和灵活性。以一个系统,提供稳定性,而系统从故障中恢复,并尽量降低此故障对性能的影响

能够帮助高速地拒绝一个操作。即非常可能失败,而不是等待操作超时或者不返回的请求,以保持系统的响应时间假设断路器提高每次改变状态的时间的事件,该信息能够被用来监測由断路器保护系统的部件的健康状况以提醒管理员断路器跳闸,以在打开状态

模式是可定制的。而且能够依据可能的故障的性质进行调整。

比如,您能够申请添加的超时时间为一个断路器

能够放置在打开状态断路器的几秒钟開始,然后,假设故障一直没有解决添加超时几分钟的时间,等等。在某些情况下。而不是打开状态返回故障并提高了异常,也可能是实用的,返回一个缺省值。该值有意义应用

问题和注意事项

在决定怎样实现这个模式时,您应考虑下面几点:

异常处理通过断路器调用操作应用程序必须准备优点理。假设该操作不可用的,能够抛出的异常

这种异常处理特定应用程序的方式

比如一个应用程序能够临时减少其功能。调用替换操作来尝试运行同样的任务获得同样的数据或者报告该异常给用户,并要求他们稍后再试

•例外的类型一个请求可能失败的原因有多种当中有一些可能指示更严重的类型的失效比其它比如,一个请求可能会失败,因为远程服务已经崩溃了,可能须要几分钟才干恢复,或失败可能是因为该服务临时超载造成的超时时间。一种断路器可能可以检查发生的异常类型。并依据这些异常的性质调整策略

比如它可能须要超时异常更大数目断路器状态相比失败次数跳闸因为服务全然不可用


日志记录。

一个断路器应记录全部失败的请求(也可能是成功的请求)。以使管理员可以监视封装了操作的健康。

可恢复性

您应该配置断路器与之相匹配的是保护的操作可能恢复模式

比如假设断路器保持在打开状态下非常长一段时间,也可能产生异常。即使对于失败的原因早已得到了解决。类似地,一个断路器能够振荡并减少应用程序的响应时间,假设它从打开状态到状态太快切换

•測试失败的操作在打开状态下,而不是使用一个计时器来确定何时切换到开放状态下,断路器可取代周期性地查验远程服务资源,以确定它是否已经再次变得可用。这个能够採取企图的形式援引了曾经失败的操作,也能够使用由远程服务提供的特殊操作专门用于測试服务的健康状况。所描写叙述的卫生端点监測图案


手动覆盖一个系统中,假设恢复时间一个失败的操作可变的,可能是有利的。以提供一个手动复位选项,使管理员可以强行关闭断路器(和复位故障计数器相同。管理员可以强制断路器进入开放状态(并又一次启动超时定时器,假设由断路器保护动作临时不可用。

并发

同样的电路断路器能够通过大量的应用程序的并行实例来訪问。实施不应该堵塞并发请求或加入过多的开销,以每次调用操作


•资源分化使用单个断路器时,一个类型的资源,假设​​可能有多个潜在的独立供应商要小心

比如在数据存储器。其包含多个碎片1分片能够是全然可訪问的,而还有一个经历一个临时的问题假设这些情况下错误响应被合二为一。应用程序可能试图訪问一些碎片,即使发生问题的可能性高。同一时候获得其它碎片。即使它是可能成功的可能被阻塞

•加速断路有时失败响应能够包括足够的信息用于断路器的实施知道应当马上跳闸并保持处于跳闸状态最小时间量比如。从过载的共享资源错误响应能够指示马上重试时不推荐使用,而且该应用程序应取代再次尝试几分钟时间

Note

HTTP协议定义的“HTTP503服务不可用”。它能够所请求的服务当前不可用的特定的Webserver上的被返回的响应。响应能够包含附加信息,比如延迟的预期持续时间。

重播失败的请求

在打开状态下,而不是简单的故障非常快断路器也能够记录每一个请求的具体信息,以轴颈安排这些请求时。远程资源服务变得可用重放。

外部服务不当超时电路断路器可能无法充分保护的应用程序,从失败配置有一个漫长的超时时间对外服务业务假设超时太长,执行一个断路器的螺纹可能被阻塞长时间之前断路器指示操作已失败。这个时候更多的应用程序实例能够尝试通过断路器来调用服务,并占用一个显著的线程数之前。他们都失败。


使用这个模式

使用这样的模式
•为了防止一个应用程序试图调用一个远程服务訪问共享资源,假设​​该操作极有可能失败。

这样的模式可能不适合
对于处理的应用程序訪问本地专用资源,比如存储器内数据结构

在这样的环境下使用断路器仅仅会添加开销到您的系统

•作为一个替代品来处理异常在应用程序业务逻辑。

样例

在Web应用中几个页面的已填充了从外部服务中检索数据

假设该系统实现了最小的缓存点击率最高的每一个页面都会导致往返服务从Web应用程序到服务的连接能够用一个超时时间段(通常为60秒)进行配置,而且假设该服务没有这个时间响应每一个网页逻辑将假设该服务不可用,而且抛出异常。

可是,假设服务失败,系统很繁忙用户可能会被迫等待异常发生时长达60秒终于的资源,如内存。连接和线程可能被耗尽以防止其它用户连接到系统即使它们没有訪问检索业务数据的页面

通过加入很多其它的Webserver和运行负载均衡扩展,系统可能会延误的点资源趋于枯竭但它不会解决这个问题,由于用户请求仍然会反应迟钝,全部的Webserver仍然能够终于耗尽资源

包裹连接到服务,并检索数据的断路器逻辑能够帮助缓解这个问题的影响。而且更优雅处理服务故障用户请求仍然会失败的,但它们将更加迅速地失败,而且资源不会被堵塞

CircuitBreaker类维护有关对象,它实现以下的代码所看到的ICircuitBreakerStateStore接口电路断路器的状态信息。

interface ICircuitBreakerStateStore
{
  CircuitBreakerStateEnum State { get; }

  Exception LastException { get; }

  DateTime LastStateChangedDateUtc { get; }

  void Trip(Exception ex);

  void Reset();

  void HalfOpen();

  bool IsClosed { get; }
}

 

状态属性指示断路器的当前状态,以及由CircuitBreakerStateEnum枚举所定义的将是这些值中的一个程序HalfOpen或者已关闭

假设电路断路器闭合假设其打开或半开IsClosed属性应该是真实的跳闸方法切换断路器为打开状态的状态。并记录该引起状态变化异常所发生的异常的日期和时间一起

LastExceptionLastStateChangedDateUtc属性返回此信息。

复位方法关闭断路器HalfOpen方法将断路器半开

在该实例中InMemoryCircuitBreakerStateStore类包括ICircuitBreakerStateStore接口的实现CircuitBreaker类创建这个类的一个实例来保存断路器的状态

CircuitBreakerExecuteAction方法包装的操作(在Action托付形式)可能会失败。该方法执行时。它首先检查断路器的状态假设它被关闭(当地IsOpen属性假设断路器处于打开状态半开,返回是假的ExecuteAction方法试图调用Action托付假设此操作失败异常处理程序执行TrackException方法。用于设置该电路断路器的状态通过调用InMemoryCircuitBreakerStateStore物体的行程的方法打开

以下的代码演示样例强调了这一流程。

 

public class CircuitBreaker
{
  private readonly ICircuitBreakerStateStore stateStore =
    CircuitBreakerStateStoreFactory.GetCircuitBreakerStateStore();

  private readonly object halfOpenSyncObject = new object ();
  ...
  public bool IsClosed { get { return stateStore.IsClosed; } }

  public bool IsOpen { get { return !IsClosed; } }

  public void ExecuteAction(Action action)
  {
    ...
    if (IsOpen)
    {
      // The circuit breaker is Open.
      ... (see code sample below for details)
    }

    // The circuit breaker is Closed, execute the action.
    try
    {
      action();
    }
    catch (Exception ex)
    {
      // If an exception still occurs here, simply 
      // re-trip the breaker immediately.
      this.TrackException(ex);

      // Throw the exception so that the caller can tell
      // the type of exception that was thrown.
      throw;
    }
  }

  private void TrackException(Exception ex)
  {
    // For simplicity in this example, open the circuit breaker on the first exception.
    // In reality this would be more complex. A certain type of exception, such as one
    // that indicates a service is offline, might trip the circuit breaker immediately. 
    // Alternatively it may count exceptions locally or across multiple instances and
    // use this value over time, or the exception/success ratio based on the exception
    // types, to open the circuit breaker.
    this.stateStore.Trip(ex);
  }
}

 

以下的样例显示了运行,假设断路器没有关闭代码前面的样例中省略假设断路器已经开了一段时间长于当地OpenToHalfOpenWaitTime字段中CircuitBreaker类中指定的时间首先检查

假设是这样的情况。则ExecuteAction方法设置断路器半开然后尝试运行该行动代表所指定的操作

假设操作成功,则断路器复位到闭合状态

假设操作失败,则跳闸恢复到打开状态,而且发生被更新。以使断路器将等待进一步期间再次尝试运行该操作之前异常所需的时间。

假设断路器至今仅仅有开放的时间非常短小于OpenToHalfOpenWaitTime值时,ExecuteAction方法简单地抛出CircuitBreakerOpenException异常返回引发断路器转换到打开状态的误差。

此外,为了防止断路器试图运行并发呼叫操作。同一时候半开的,它使用一个锁。试图调用该操作假设断路器是公开进行处理,如后所述,它会失败并异常。

  ...
    if (IsOpen)
    {
      // The circuit breaker is Open. Check if the Open timeout has expired.
      // If it has, set the state to HalfOpen. Another approach may be to simply 
      // check for the HalfOpen state that had be set by some other operation.
      if (stateStore.LastStateChangedDateUtc + OpenToHalfOpenWaitTime < DateTime.UtcNow)
      {
        // The Open timeout has expired. Allow one operation to execute. Note that, in
        // this example, the circuit breaker is simply set to HalfOpen after being 
        // in the Open state for some period of time. An alternative would be to set 
        // this using some other approach such as a timer, test method, manually, and 
        // so on, and simply check the state here to determine how to handle execution
        // of the action. 
        // Limit the number of threads to be executed when the breaker is HalfOpen.
        // An alternative would be to use a more complex approach to determine which
        // threads or how many are allowed to execute, or to execute a simple test 
        // method instead.
        bool lockTaken = false;
        try
        {
          Monitor.TryEnter(halfOpenSyncObject, ref lockTaken)
          if (lockTaken)
          {
            // Set the circuit breaker state to HalfOpen.
            stateStore.HalfOpen();

            // Attempt the operation.
            action();

            // If this action succeeds, reset the state and allow other operations.
            // In reality, instead of immediately returning to the Open state, a counter
            // here would record the number of successful operations and return the
            // circuit breaker to the Open state only after a specified number succeed.
            this.stateStore.Reset();
            return;
          }
          catch (Exception ex)
          {
            // If there is still an exception, trip the breaker again immediately.
            this.stateStore.Trip(ex);

            // Throw the exception so that the caller knows which exception occurred.
            throw;
          }
          finally
          {
            if (lockTaken)
            {
              Monitor.Exit(halfOpenSyncObject);
            }
          }
        }
      }
      // The Open timeout has not yet expired. Throw a CircuitBreakerOpen exception to
      // inform the caller that the caller that the call was not actually attempted, 
      // and return the most recent exception received.
      throw new CircuitBreakerOpenException(stateStore.LastException);
    }
    ...

 

使用CircuitBreaker对象,以保护操作时。应用程序创建CircuitBreaker类的一个实例,并调用ExecuteAction方法,指定操作作为參数来运行

该应用程序应该准备。假设操作失败,由于断路器处于打开状态,以赶上CircuitBreakerOpenException例外。

以下的代码显示了一个演示样例

var breaker = new CircuitBreaker();

try
{
  breaker.ExecuteAction(() =>
  {
    // Operation protected by the circuit breaker.
    ...
  });
}
catch (CircuitBreakerOpenException ex)
{
  // Perform some different action when the breaker is open.
  // Last exception details are in the inner exception.
  ...
}
catch (Exception ex)
{
  ...
}

 

本文翻译自MSDN:http://msdn.microsoft.com/en-us/library/dn589784.aspx