处理故障连接到远程服务或资源时,可能需要耗费大量的时间。这种模式可以提高应用程序的稳定性和灵活性。
在分布式环境中,如在云,其中,应用程序执行访问远程资源和服务的操作,有可能对这些操作的失败是由于瞬时故障,如慢的网络连接,超时,或者被过度使用的资源或暂时不可用。这些故障一般之后的短时间内纠正自己,和一个强大的云应用应该准备使用的策略来处理它们,例如,通过重试模式进行说明。
但是,也可以是其中的故障是由于那些不容易预见的突发事件的情况下,这可能需要更长的时间来纠正。这些故障从连接的部分损失到服务的完整的故障范围的严重程度。在这种情况下,它可能是毫无意义的应用,不断重试执行的操作是不太可能成功,而不是应用程序应该很快接受该操作已失败,并相应地处理这个故障。
此外,如果一个服务是非常繁忙的,在系统中的一个部分出现故障可能会导致连锁故障。例如,调用一个服务的操作可被配置成实现一个超时,如果该服务无法在这段时间内响应一个失败消息答复。然而,这一策略可能导致许多并发请求的相同的操作,直到超时时段期满被阻止。这些被禁止的请求可能会持有关键系统资源,如内存,线程,数据库连接等。因此,这些资源可能会耗尽,从而导致该系统的其他可能无关的部件,需要使用相同的资源的失败。在这些情况下,这将是优选的操作立即失败,并且只尝试调用服务,如果它是可能成功。注意,设置一个较短的超时可能有助于解决此问题,但在超时不应该如此之短,以致操作失败的大部分时间,即使该请求到服务最终会成功。
该断路器图案可以防止一个应用程序多次试图执行一个操作,即很可能失败,允许它继续而不等待故障恢复或者浪费 CPU 周期,而它确定该故障是持久的。断路器模式也使应用程序能够检测故障是否已经解决。如果问题似乎已经得到纠正,应用程序可以尝试调用操作。
注意
断路器图案的目的是从该重试模式的不同。重试模式使应用程序可以重试操作以期望它会成功。断路器图案防止应用程序执行一个操作,即很可能失败。一个应用程序可以通过使用重试模式,通过一个断路器调用操作结合这两种模式。然而,在重试逻辑应该是由断路器返回任何异常敏感和放弃重试次数,如果断路器指示故障不是瞬时的。
断路器充当可能失败操作的代理。代理应监测最近发生的故障数量,然后使用这个信息来决定是否允许该操作继续进行,或简单地立即返回一个异常。
代理可以被实现为状态机与模拟的电路断路器的功能如下状态:
关闭:从应用程序的请求是通过对操作进行路由。代理保持最近的失败次数的计数,并且如果该呼叫到操作不成功,则代理递增该计数。如果最近的失败次数超过了一个给定时间周期内的规定的阈值时,该代理将被置于打开状态。在这一点上的代理启动一个超时定时器,当该定时器期满的代理放置到半开放状态。
注意
超时定时器的目的是为了给系统时间,纠正允许应用程序尝试再次执行该操作之前导致失败的问题。
注意
半开的状态是很有用的,以防止恢复服务,从突然被淹没的请求。作为服务恢复,也可能是能够支持请求的限制音量,直到恢复完成,但在恢复过程中,海量的工作可能会导致服务超时或再次失败。
所用的封闭状态下的失败计数器是基于时间的。它以定期自动复位。这有助于防止断路器进入打开状态,如果它经受偶然的失败;这使断路器跳闸进入打开状态的故障阈值时,故障的指定数量的指定的时间间隔期间发生的仅达到。所使用的半开状态下的成功计数器记录成功尝试调用的操作的数量。断路器恢复到封闭状态后的连续操作调用中指定数量的已成功。如果任何调用失败时,断路器立即进入打开状态,并且成功的计数器将其进入半开状态下一次复位。
如何将系统恢复从外部处理,可能通过恢复或重新启动故障部件或修理的网络连接。
执行断路器图案增加了稳定性和灵活性,以一个系统,提供稳定性,而系统从故障中恢复,并尽量减少此故障的对性能的影响。它可以帮助快速地拒绝对一个操作,即很可能失败,而不是等待操作超时(或者不返回)的请求,以保持系统的响应时间。如果断路器提高每次改变状态的时间的事件,该信息可以被用来监测由断路器保护系统的部件的健康状况,或以提醒管理员当断路器跳闸,以在打开状态。
模式是可定制的,并且可以根据可能的故障的性质进行调整。例如,您可以申请增加的超时时间为一个断路器。可以放置在打开状态的断路器的几秒钟开始,然后,如果故障一直没有解决增加超时到几分钟的时间,等等。在某些情况下,而不是打开状态返回故障并提高了异常,也可能是有用的,返回一个缺省值,该值是有意义的应用。
在决定如何实现这个模式时,您应考虑以下几点:
HTTP 协议定义的“HTTP503 服务不可用”,它可以如所请求的服务是当前不可用的特定的 Web 服务器上的被返回的响应。此响应可以包括附加信息,例如延迟的预期持续时间。
重播失败的请求。在打开状态下,而不是简单的故障很快,断路器也可以记录每个请求的详细信息,以轴颈和安排这些请求时,远程资源或服务变得可用重放。
使用这种模式:
这种模式可能不适合:
在 Web 应用中,几个页面的已填充了从外部服务中检索数据。如果该系统实现了最小的缓存,点击率最高的为每个页面都会导致往返服务。从 Web 应用程序到服务的连接可以用一个超时时间段(通常为 60 秒)进行配置,并且如果该服务没有在这个时间响应在每个网页的逻辑将假设该服务不可用,并且抛出异常。
但是,如果服务失败,系统非常繁忙,用户可能会被迫等待异常发生时长达60秒前。最终的资源,如内存,连接和线程可能被耗尽,以防止其他用户连接到系统,即使它们没有访问检索业务数据的页面。
通过添加更多的 Web 服务器和执行负载均衡扩展,系统可能会延误的点资源趋于枯竭,但它不会解决问题,因为用户请求仍然会反应迟钝,所有的 Web 服务器仍然可以最终耗尽资源。
包裹连接到服务,并检索数据中的断路器的逻辑可以帮助缓解这个问题的影响,并且更优雅处理服务故障。用户请求仍然会失败的,但它们将更加迅速地失败,并且资源不会被阻塞。
该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 属性应该是真实的。跳闸方法切换断路器为打开状态的状态,并记录该引起状态变化的异常,与所发生的异常的日期和时间一起。该 LastException 和 LastStateChangedDateUtc 属性返回此信息。复位方法关闭断路器和 HalfOpen 方法将断路器半开。
在该实例中 InMemoryCircuitBreakerStateStore 类包含 ICircuitBreakerStateStore 接口的实现。该 CircuitBreaker 类创建这个类的一个实例来保存断路器的状态。
在 CircuitBreaker 类的 ExecuteAction 方法包装的操作(在 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) { ... }