Patrón de disyuntorCircuit Breaker pattern

Controla los errores cuya recuperación puede tardar una cantidad variable de tiempo durante la conexión a un recurso o servicio remoto.Handle faults that might take a variable amount of time to recover from, when connecting to a remote service or resource. Puede mejorar la estabilidad y la resistencia de una aplicación.This can improve the stability and resiliency of an application.

Contexto y problemaContext and problem

En un entorno distribuido, las llamadas a los servicios y los recursos remotos pueden producir un error debido a errores transitorios, como son las conexiones de red lentas, el agotamiento de los tiempos de espera o los recursos que se sobrecargan o no están disponibles temporalmente.In a distributed environment, calls to remote resources and services can fail due to transient faults, such as slow network connections, timeouts, or the resources being overcommitted or temporarily unavailable. Estos errores suelen corregirse por sí mismos tras un breve período de tiempo y una aplicación sólida en la nube debe estar preparada para controlarlos mediante una estrategia como la del patrón Retry (reintento).These faults typically correct themselves after a short period of time, and a robust cloud application should be prepared to handle them by using a strategy such as the Retry pattern.

Sin embargo, también puede haber situaciones en las que los errores se deban a eventos no anticipados y cuya corrección puede tardar mucho más tiempo.However, there can also be situations where faults are due to unanticipated events, and that might take much longer to fix. La gravedad de estos errores puede abarcar desde una pérdida parcial de la conectividad hasta la total detención de un servicio.These faults can range in severity from a partial loss of connectivity to the complete failure of a service. En estas situaciones, es posible que no tenga sentido para una aplicación volver a intentar continuamente una operación que no es probable que pueda funcionar de modo correcto y, en su lugar, deba admitir este hecho con rapidez y tratar este error en consecuencia.In these situations it might be pointless for an application to continually retry an operation that is unlikely to succeed, and instead the application should quickly accept that the operation has failed and handle this failure accordingly.

Además, si un servicio está muy ocupado, el error en una parte del sistema podría provocar errores en cascada.Additionally, if a service is very busy, failure in one part of the system might lead to cascading failures. Por ejemplo, una operación que invoca un servicio puede configurarse para implementar un tiempo de espera y responder con un mensaje de error si el servicio no responde dentro de este período.For example, an operation that invokes a service could be configured to implement a timeout, and reply with a failure message if the service fails to respond within this period. Sin embargo, esta estrategia puede desencadenar muchas solicitudes simultáneas a la misma operación para que se bloquee hasta que expire el período de tiempo de espera.However, this strategy could cause many concurrent requests to the same operation to be blocked until the timeout period expires. Estas solicitudes bloqueadas pueden contener recursos críticos del sistema, tales como memoria, subprocesos o conexiones de base de datos, entre otros.These blocked requests might hold critical system resources such as memory, threads, database connections, and so on. Por lo tanto, estos recursos podrían agotarse y provocar errores de otras partes posiblemente no relacionadas del sistema que tenga que usar los mismos recursos.Consequently, these resources could become exhausted, causing failure of other possibly unrelated parts of the system that need to use the same resources. En estas situaciones, podría ser preferible para la operación dejar de funcionar de inmediato y solo intentar invocar el servicio si es probable que pueda ejecutarse correctamente.In these situations, it would be preferable for the operation to fail immediately, and only attempt to invoke the service if it's likely to succeed. Tenga en cuenta que establecer un tiempo de espera menor podría ayudar a resolver este problema, pero no debería ser tan corto como para que la operación dé error en la mayoría de los casos, incluso aunque la solicitud al servicio finalmente pudiera realizarse correctamente.Note that setting a shorter timeout might help to resolve this problem, but the timeout shouldn't be so short that the operation fails most of the time, even if the request to the service would eventually succeed.

SoluciónSolution

El patrón Circuit Breaker, popularizado por Michael Nygard en su libro Release It!, puede impedir que una aplicación intente repetidamente ejecutar una operación que tenga probabilidad de dar error.The Circuit Breaker pattern, popularized by Michael Nygard in his book, Release It!, can prevent an application from repeatedly trying to execute an operation that's likely to fail. Ello le permite continuar sin esperar a corregir el error ni desperdiciar ciclos de CPU mientras se determina si el error continuará durante mucho tiempo.Allowing it to continue without waiting for the fault to be fixed or wasting CPU cycles while it determines that the fault is long lasting. El patrón Circuit Breaker también permite a una aplicación detectar si el error se ha resuelto.The Circuit Breaker pattern also enables an application to detect whether the fault has been resolved. Si el problema parece haberse corregido, la aplicación puede intentar invocar la operación.If the problem appears to have been fixed, the application can try to invoke the operation.

El propósito del patrón Circuit Breaker difiere de la finalidad del patrón Retry.The purpose of the Circuit Breaker pattern is different than the Retry pattern. El patrón Retry permite a una aplicación volver a intentar una operación esperando que se podrá ejecutar correctamente.The Retry pattern enables an application to retry an operation in the expectation that it'll succeed. El patrón Circuit Breaker impide que una aplicación realice una operación que probablemente produzca errores.The Circuit Breaker pattern prevents an application from performing an operation that is likely to fail. Una aplicación puede combinar estos dos patrones usando Retry para invocar una operación a través de un disyuntor.An application can combine these two patterns by using the Retry pattern to invoke an operation through a circuit breaker. Sin embargo, la lógica de reintento debe tener en cuenta las excepciones devueltas por el disyuntor y dejar de reintentar la operación si este indica que un error no es transitorio.However, the retry logic should be sensitive to any exceptions returned by the circuit breaker and abandon retry attempts if the circuit breaker indicates that a fault is not transient.

Un disyuntor actúa como un proxy para las operaciones que podrían producir errores.A circuit breaker acts as a proxy for operations that might fail. El proxy debe supervisar el número de errores recientes que se han producido y utilizar esta información para decidir si permitir que continúe la operación, o simplemente devolver de inmediato una excepción.The proxy should monitor the number of recent failures that have occurred, and use this information to decide whether to allow the operation to proceed, or simply return an exception immediately.

El proxy se puede implementar como una máquina de estados con los siguientes estados que imitan la funcionalidad de un disyuntor eléctrico:The proxy can be implemented as a state machine with the following states that mimic the functionality of an electrical circuit breaker:

  • Closed (Cerrado): la solicitud de la aplicación se enruta a la operación.Closed: The request from the application is routed to the operation. El proxy mantiene un recuento del número de errores recientes y, si la llamada a la operación se realiza correctamente, el proxy incrementa este recuento.The proxy maintains a count of the number of recent failures, and if the call to the operation is unsuccessful the proxy increments this count. Si el número de errores recientes supera un umbral especificado en un período de tiempo determinado, el proxy se coloca en el estado Open (abierto).If the number of recent failures exceeds a specified threshold within a given time period, the proxy is placed into the Open state. En este momento, el proxy inicia un temporizador de tiempo de espera y, cuando este temporizador expira, se coloca en el estado Half-open (semiabierto).At this point the proxy starts a timeout timer, and when this timer expires the proxy is placed into the Half-Open state.

    El propósito del temporizador de tiempo de espera es conceder al sistema tiempo para corregir el problema que provocó el error, antes de permitir que la aplicación intente realizar la operación de nuevo.The purpose of the timeout timer is to give the system time to fix the problem that caused the failure before allowing the application to try to perform the operation again.

  • Abierta: la solicitud de la aplicación produce un error inmediatamente y se devuelve una excepción a la aplicación.Open: The request from the application fails immediately and an exception is returned to the application.

  • Half-open (Semiabierto): se permite pasar un número limitado de solicitudes de la aplicación e invocar la operación.Half-Open: A limited number of requests from the application are allowed to pass through and invoke the operation. Si estas solicitudes se realizan correctamente, se supone que la causa del error se ha corregido y el disyuntor cambia al estado Closed (cerrado) y el número de errores se restablece.If these requests are successful, it's assumed that the fault that was previously causing the failure has been fixed and the circuit breaker switches to the Closed state (the failure counter is reset). Si alguna solicitud da error, el disyuntor supone que el anterior error no se solucionó y vuelve al estado Open (abierto) y reinicia el temporizador de tiempo de espera para dar al sistema más tiempo para recuperarse.If any request fails, the circuit breaker assumes that the fault is still present so it reverts back to the Open state and restarts the timeout timer to give the system a further period of time to recover from the failure.

    El estado Half-open (semiabierto) es útil para impedir que un servicio de recuperación se inunde de repente con solicitudes.The Half-Open state is useful to prevent a recovering service from suddenly being flooded with requests. A medida que un servicio se recupera, podría ser capaz de admitir un volumen limitado de solicitudes hasta que la recuperación se completa, pero, mientras está en curso, una saturación de trabajo puede hacer que el servicio agote el tiempo de espera o dé error de nuevo.As a service recovers, it might be able to support a limited volume of requests until the recovery is complete, but while recovery is in progress a flood of work can cause the service to time out or fail again.

Estados de Circuit Breaker

En la ilustración, el contador de errores que usa el estado Closed (cerrado) depende del tiempo.In the figure, the failure counter used by the Closed state is time based. Se restablece automáticamente a intervalos periódicos.It's automatically reset at periodic intervals. Esto ayuda a impedir que el disyuntor entre en el estado Open (abierto) si experimenta errores ocasionales.This helps to prevent the circuit breaker from entering the Open state if it experiences occasional failures. El umbral de error que se encuentra con el disyuntor en el estado Open (abierto) solo se alcanza cuando se produce un número especificado de errores durante un intervalo especificado.The failure threshold that trips the circuit breaker into the Open state is only reached when a specified number of failures have occurred during a specified interval. El contador utilizado por el estado Half-Open (semiabierto) registra el número de intentos correctos para invocar la operación.The counter used by the Half-Open state records the number of successful attempts to invoke the operation. El disyuntor vuelve al estado Closed (cerrado) después de un número especificado de llamadas consecutivas a la operación que hayan tenido éxito.The circuit breaker reverts to the Closed state after a specified number of consecutive operation invocations have been successful. Si se produce un error en alguna invocación, el disyuntor entra en el estado Open (abierto) inmediatamente y el contador de éxitos se restablecerá la próxima vez que entre en el estado Half-Open (semiabierto).If any invocation fails, the circuit breaker enters the Open state immediately and the success counter will be reset the next time it enters the Half-Open state.

Externamente, es posible que las recuperaciones del sistema se traten restaurando o reiniciando un componente erróneo o reparando una conexión de red.How the system recovers is handled externally, possibly by restoring or restarting a failed component or repairing a network connection.

El patrón Circuit Breaker proporciona estabilidad mientras el sistema se recupera de un error y minimiza el impacto en el rendimiento.The Circuit Breaker pattern provides stability while the system recovers from a failure and minimizes the impact on performance. Puede ayudar a mantener el tiempo de respuesta del sistema al rechazar rápidamente una solicitud para una operación que es probable que dé error, en lugar de esperar a que agote el tiempo de espera o no termine nunca.It can help to maintain the response time of the system by quickly rejecting a request for an operation that's likely to fail, rather than waiting for the operation to time out, or never return. Si el disyuntor genera un evento cada vez que cambia el estado, esta información puede utilizarse para supervisar el estado de la parte del sistema protegida por el disyuntor o para alertar a un administrador cuando un disyuntor se active en el estado Open (abierto).If the circuit breaker raises an event each time it changes state, this information can be used to monitor the health of the part of the system protected by the circuit breaker, or to alert an administrator when a circuit breaker trips to the Open state.

El patrón es personalizable y puede adaptarse según el tipo de errores posibles.The pattern is customizable and can be adapted according to the type of the possible failure. Por ejemplo, puede aplicar a un disyuntor un temporizador de tiempo de espera que vaya aumentando.For example, you can apply an increasing timeout timer to a circuit breaker. Puede colocar el disyuntor en el estado Open (abierto) inicialmente durante unos segundos y, a continuación, incrementar el tiempo de espera unos minutos, si el error no se ha resuelto, y así sucesivamente.You could place the circuit breaker in the Open state for a few seconds initially, and then if the failure hasn't been resolved increase the timeout to a few minutes, and so on. En algunos casos, en lugar de que el estado Open (abierto) devuelva un error y genere una excepción, puede ser útil devolver un valor predeterminado que sea significativo para la aplicación.In some cases, rather than the Open state returning failure and raising an exception, it could be useful to return a default value that is meaningful to the application.

Problemas y consideracionesIssues and considerations

A la hora de decidir cómo implementar este patrón, debe considerar los siguientes puntos:You should consider the following points when deciding how to implement this pattern:

Control de excepciones.Exception Handling. Una aplicación que invoca una operación a través de un disyuntor debe estar preparada para controlar las excepciones que se produzcan si la operación no está disponible.An application invoking an operation through a circuit breaker must be prepared to handle the exceptions raised if the operation is unavailable. La forma en que se controlan las excepciones será específica de la aplicación.The way exceptions are handled will be application specific. Por ejemplo, una aplicación podría degradar temporalmente su funcionalidad, invocar una operación alternativa para intentar realizar la misma tarea u obtener los mismos datos, o notificar la excepción al usuario y pedirle que la vuelva a intentar más tarde.For example, an application could temporarily degrade its functionality, invoke an alternative operation to try to perform the same task or obtain the same data, or report the exception to the user and ask them to try again later.

Tipos de excepciones.Types of Exceptions. Una solicitud podría producir un error por diversos motivos, algunos de los cuales podrían indicar un tipo de error más grave que otras.A request might fail for many reasons, some of which might indicate a more severe type of failure than others. Por ejemplo, una solicitud podría dar error porque un servicio remoto haya dejado de funcionar y tardará varios minutos en recuperarse, o por un tiempo de espera agotado debido a que el servicio está sobrecargado temporalmente.For example, a request might fail because a remote service has crashed and will take several minutes to recover, or because of a timeout due to the service being temporarily overloaded. Un disyuntor puede examinar los tipos de excepciones que se producen y ajustar su estrategia en función de la naturaleza de estas excepciones.A circuit breaker might be able to examine the types of exceptions that occur and adjust its strategy depending on the nature of these exceptions. Por ejemplo, podría ser necesario un mayor número de excepciones de tiempo de espera para activar el disyuntor en el estado Open (abierto) en comparación con el número de errores debidos a que el servicio no esté completamente disponible.For example, it might require a larger number of timeout exceptions to trip the circuit breaker to the Open state compared to the number of failures due to the service being completely unavailable.

Registro.Logging. Un disyuntor debe registrar todas las solicitudes con error (y, posiblemente, las correctas) para permitir que un administrador supervise el estado de la operación.A circuit breaker should log all failed requests (and possibly successful requests) to enable an administrator to monitor the health of the operation.

Capacidad de recuperación.Recoverability. Debe configurar el disyuntor para que coincida con el modelo de recuperación más probable de la operación que se protege.You should configure the circuit breaker to match the likely recovery pattern of the operation it's protecting. Por ejemplo, si el disyuntor permanece en el estado Open (abierto) durante un largo período, podría producir excepciones aunque el motivo del error se hubiese resuelto.For example, if the circuit breaker remains in the Open state for a long period, it could raise exceptions even if the reason for the failure has been resolved. De igual forma, podría fluctuar y reducir los tiempos de respuesta de las aplicaciones si pasa del estado Open a Half-Open demasiado rápidamente.Similarly, a circuit breaker could fluctuate and reduce the response times of applications if it switches from the Open state to the Half-Open state too quickly.

Prueba de las operaciones con error.Testing Failed Operations. En el estado Open, en lugar de usar un temporizador para determinar cuándo cambiar al estado Half-Open, un disyuntor puede en cambio hacer ping periódicamente al servicio remoto o al recurso para determinar si tiene que estar disponible de nuevo.In the Open state, rather than using a timer to determine when to switch to the Half-Open state, a circuit breaker can instead periodically ping the remote service or resource to determine whether it's become available again. Este ping podría adoptar la forma de intento de invocar una operación que hubiera generado el error previamente o podría usar una operación especial proporcionada por el servicio remoto de forma específica para probar el estado del servicio, como se describe en el patrón Health Endpoint Monitoring (supervisión del punto de conexión de estado).This ping could take the form of an attempt to invoke an operation that had previously failed, or it could use a special operation provided by the remote service specifically for testing the health of the service, as described by the Health Endpoint Monitoring pattern.

Invalidación manual.Manual Override. En un sistema en el que el tiempo de recuperación de una operación que da error es muy variable, es conveniente proporcionar una opción de restablecimiento manual que permita a un administrador cerrar un disyuntor (y restablecer el contador de errores).In a system where the recovery time for a failing operation is extremely variable, it's beneficial to provide a manual reset option that enables an administrator to close a circuit breaker (and reset the failure counter). De forma similar, un administrador podría forzar que un disyuntor pase al estado Open y reiniciar así el temporizador de tiempo de espera, si la operación protegida por el disyuntor no está disponible temporalmente.Similarly, an administrator could force a circuit breaker into the Open state (and restart the timeout timer) if the operation protected by the circuit breaker is temporarily unavailable.

Simultaneidad.Concurrency. El mismo disyuntor podría tener acceso a un gran número de instancias simultáneas de una aplicación.The same circuit breaker could be accessed by a large number of concurrent instances of an application. La implementación no debe bloquear las solicitudes simultáneas ni agregar una sobrecarga excesiva a cada llamada a una operación.The implementation shouldn't block concurrent requests or add excessive overhead to each call to an operation.

Diferenciación de los recursos.Resource Differentiation. Tenga cuidado al usar un único disyuntor para un tipo de recurso si puede haber varios proveedores independientes subyacentes.Be careful when using a single circuit breaker for one type of resource if there might be multiple underlying independent providers. Por ejemplo, en un almacén de datos que contenga varias particiones, una podría ser totalmente accesible mientras otra experimenta un problema temporal.For example, in a data store that contains multiple shards, one shard might be fully accessible while another is experiencing a temporary issue. Si se combinan las respuestas de error en estos casos, una aplicación podría intentar acceder a algunas particiones incluso cuando fuese muy probable que se produjera un error, mientras que el acceso a las otras podría bloquearse, aunque hubiera mucha probabilidad de que no sufrieran problemas.If the error responses in these scenarios are merged, an application might try to access some shards even when failure is highly likely, while access to other shards might be blocked even though it's likely to succeed.

Disyuntor acelerado.Accelerated Circuit Breaking. A veces, una respuesta de error puede contener suficiente información para que el disyuntor se active inmediatamente y permanezca así una cantidad mínima de tiempo.Sometimes a failure response can contain enough information for the circuit breaker to trip immediately and stay tripped for a minimum amount of time. Por ejemplo, la respuesta de error de un recurso compartido que está sobrecargado podría indicar que no se recomienda un reintento inmediato y que la aplicación, en cambio, debe intentarse de nuevo al cabo de unos minutos.For example, the error response from a shared resource that's overloaded could indicate that an immediate retry isn't recommended and that the application should instead try again in a few minutes.

Nota

Un servicio puede devolver HTTP 429 (demasiadas solicitudes), si está limitando al cliente, o HTTP 503 (servicio no disponible), si el servicio no está disponible actualmente.A service can return HTTP 429 (Too Many Requests) if it is throttling the client, or HTTP 503 (Service Unavailable) if the service is not currently available. La respuesta puede incluir información adicional, como la duración prevista del retraso.The response can include additional information, such as the anticipated duration of the delay.

Respuesta de las solicitudes con error.Replaying Failed Requests. En el estado Open, en lugar de generar un error rápidamente, un disyuntor también puede registrar los detalles de cada solicitud en un diario y hacer que estas solicitudes se reproduzcan cuando el servicio o el recurso remoto estén disponibles.In the Open state, rather than simply failing quickly, a circuit breaker could also record the details of each request to a journal and arrange for these requests to be replayed when the remote resource or service becomes available.

Tiempos de espera inadecuados en servicios externos.Inappropriate Timeouts on External Services. Un disyuntor podría no ser capaz de proteger completamente las aplicaciones de las operaciones que generan errores en los servicios externos configurados con un período prolongado de tiempo de espera.A circuit breaker might not be able to fully protect applications from operations that fail in external services that are configured with a lengthy timeout period. Si el tiempo de espera es demasiado largo, un subproceso que ejecuta un disyuntor podría bloquearse durante un largo período antes de que este indique que la operación ha fracasado.If the timeout is too long, a thread running a circuit breaker might be blocked for an extended period before the circuit breaker indicates that the operation has failed. En este momento, muchas otras instancias de aplicaciones también podrían intentar invocar el servicio a través del disyuntor y ocupar un número significativo de subprocesos antes de que todos den error.In this time, many other application instances might also try to invoke the service through the circuit breaker and tie up a significant number of threads before they all fail.

Cuándo usar este patrónWhen to use this pattern

Use este patrón:Use this pattern:

  • Para evitar que una aplicación intente invocar un servicio remoto o acceda a un recurso compartido, si es muy probable que esta operación produzca un error.To prevent an application from trying to invoke a remote service or access a shared resource if this operation is highly likely to fail.

No se recomienda este patrón:This pattern isn't recommended:

  • Para el control del acceso a los recursos locales privados en una aplicación, como la estructura de datos en memoria.For handling access to local private resources in an application, such as in-memory data structure. En este entorno, el uso de un disyuntor agregaría sobrecarga al sistema.In this environment, using a circuit breaker would add overhead to your system.
  • Como sustituto para controlar las excepciones en la lógica empresarial de las aplicaciones.As a substitute for handling exceptions in the business logic of your applications.

EjemploExample

En una aplicación web, algunas de las páginas se rellenan con los datos recuperados de un servicio externo.In a web application, several of the pages are populated with data retrieved from an external service. Si el sistema implementa el almacenamiento en caché mínimo, más visitas a estas páginas provocarán un recorrido de ida y vuelta al servicio.If the system implements minimal caching, most hits to these pages will cause a round trip to the service. Las conexiones desde la aplicación web al servicio podrían configurarse con un período de tiempo de espera (normalmente, 60 segundos) y, si el servicio no responde en este tiempo, la lógica de cada página web asumirá que el servicio no está disponible y generará una excepción.Connections from the web application to the service could be configured with a timeout period (typically 60 seconds), and if the service doesn't respond in this time the logic in each web page will assume that the service is unavailable and throw an exception.

Sin embargo, si se produce un error en el servicio y el sistema está muy ocupado, los usuarios se verán obligados a esperar hasta 60 segundos antes de que se produzca una excepción.However, if the service fails and the system is very busy, users could be forced to wait for up to 60 seconds before an exception occurs. Al final, los recursos como la memoria, las conexiones y los subprocesos podrían agotarse, lo que impediría que otros usuarios se conectaran al sistema, incluso si no estuvieran accediendo a páginas que recuperen datos del servicio.Eventually resources such as memory, connections, and threads could be exhausted, preventing other users from connecting to the system, even if they aren't accessing pages that retrieve data from the service.

El escalado del sistema agregando más servidores web e implementando el equilibrio de carga podría demorarse al agotarse los recursos, pero no resolverá el problema porque las solicitudes de los usuarios seguirán sin responder y todos los servidores web podrían quedarse finalmente sin recursos.Scaling the system by adding further web servers and implementing load balancing might delay when resources become exhausted, but it won't resolve the issue because user requests will still be unresponsive and all web servers could still eventually run out of resources.

Ajustar la lógica que se conecta al servicio y recupera los datos en un disyuntor puede contribuir a solucionar este problema y controlar los errores del servicio de un modo más elegante.Wrapping the logic that connects to the service and retrieves the data in a circuit breaker could help to solve this problem and handle the service failure more elegantly. Las solicitudes de los usuarios seguirán sin ser satisfechas, pero el error se reconocerá más rápidamente y los recursos no se bloquearán.User requests will still fail, but they'll fail more quickly and the resources won't be blocked.

La clase CircuitBreaker mantiene la información de estado sobre un disyuntor en un objeto que implementa la interfaz ICircuitBreakerStateStore que se muestra en el código siguiente.The CircuitBreaker class maintains state information about a circuit breaker in an object that implements the ICircuitBreakerStateStore interface shown in the following code.

interface ICircuitBreakerStateStore
{
  CircuitBreakerStateEnum State { get; }

  Exception LastException { get; }

  DateTime LastStateChangedDateUtc { get; }

  void Trip(Exception ex);

  void Reset();

  void HalfOpen();

  bool IsClosed { get; }
}

La propiedad State indica el estado actual del disyuntor y será Open (abierto), HalfOpen (semiabierto) o Closed (cerrado) según se defina en la enumeración CircuitBreakerStateEnum.The State property indicates the current state of the circuit breaker, and will be either Open, HalfOpen, or Closed as defined by the CircuitBreakerStateEnum enumeration. La propiedad IsClosed debe ser true si el disyuntor está cerrado, y false si está abierto o semiabierto.The IsClosed property should be true if the circuit breaker is closed, but false if it's open or half open. El método Trip cambia el estado del disyuntor a Open y registra la excepción que produjo el cambio de estado, junto con la fecha y la hora.The Trip method switches the state of the circuit breaker to the open state and records the exception that caused the change in state, together with the date and time that the exception occurred. Las propiedades LastException y LastStateChangedDateUtc devuelven esta información.The LastException and the LastStateChangedDateUtc properties return this information. El método Reset cierra el disyuntor y el método HalfOpen establece el disyuntor en semiabierto.The Reset method closes the circuit breaker, and the HalfOpen method sets the circuit breaker to half open.

La clase InMemoryCircuitBreakerStateStore del ejemplo contiene una implementación de la interfaz ICircuitBreakerStateStore.The InMemoryCircuitBreakerStateStore class in the example contains an implementation of the ICircuitBreakerStateStore interface. La clase CircuitBreaker crea una instancia de esta clase para contener el estado del disyuntor.The CircuitBreaker class creates an instance of this class to hold the state of the circuit breaker.

El método ExecuteAction de la clase CircuitBreaker encapsula una operación, especificada como un delegado Action.The ExecuteAction method in the CircuitBreaker class wraps an operation, specified as an Action delegate. Si se cierra el disyuntor, ExecuteAction invoca el delegado Action.If the circuit breaker is closed, ExecuteAction invokes the Action delegate. Si se produce un error en la operación, un controlador de excepciones llama a TrackException, que establece el estado del disyuntor en Open.If the operation fails, an exception handler calls TrackException, which sets the circuit breaker state to open. En el siguiente ejemplo de código se resalta este flujo.The following code example highlights this flow.

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
      // retrip 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 might 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);
  }
}

En el ejemplo siguiente se muestra el código (omitido en el ejemplo anterior) que se ejecuta si no se cierra el disyuntor.The following example shows the code (omitted from the previous example) that is executed if the circuit breaker isn't closed. En primer lugar, comprueba si el disyuntor ha permanecido abierto durante un período mayor que el tiempo especificado por el campo local OpenToHalfOpenWaitTime de la clase CircuitBreaker.It first checks if the circuit breaker has been open for a period longer than the time specified by the local OpenToHalfOpenWaitTime field in the CircuitBreaker class. Si este es el caso, el método ExecuteAction establece el disyuntor en semiabierto; a continuación, intenta realizar la operación especificada por el delegado Action.If this is the case, the ExecuteAction method sets the circuit breaker to half open, then tries to perform the operation specified by the Action delegate.

Si la operación se realiza correctamente, el disyuntor se restablece al estado cerrado.If the operation is successful, the circuit breaker is reset to the closed state. Si se produce un error, se activa de nuevo el estado abierto y la hora en que se produjo la excepción se actualiza para que el disyuntor espere durante un período mayor, antes de intentar realizar la operación de nuevo.If the operation fails, it is tripped back to the open state and the time the exception occurred is updated so that the circuit breaker will wait for a further period before trying to perform the operation again.

Si el disyuntor solo se ha abierto durante un breve período, menor que el valor OpenToHalfOpenWaitTime, el método ExecuteAction simplemente genera una excepción CircuitBreakerOpenException y devuelve el error que provocó que el disyuntor pasara al estado abierto.If the circuit breaker has only been open for a short time, less than the OpenToHalfOpenWaitTime value, the ExecuteAction method simply throws a CircuitBreakerOpenException exception and returns the error that caused the circuit breaker to transition to the open state.

Además, utiliza un bloqueo para evitar que el disyuntor trate de realizar llamadas simultáneas a la operación mientras esté semiabierto.Additionally, it uses a lock to prevent the circuit breaker from trying to perform concurrent calls to the operation while it's half open. Si simultáneamente se intenta invocar la operación, se tratará como si el disyuntor estuviera abierto y se seguirá generando una excepción, tal y como se describe más adelante.A concurrent attempt to invoke the operation will be handled as if the circuit breaker was open, and it'll fail with an exception as described later.

    ...
    if (IsOpen)
    {
      // The circuit breaker is Open. Check if the Open timeout has expired.
      // If it has, set the state to HalfOpen. Another approach might be to
      // 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 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 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 Closed state, a counter
            // here would record the number of successful operations and return the
            // circuit breaker to the Closed state only after a specified number succeed.
            this.stateStore.Reset();
            return;
          }
        }
        catch (Exception ex)
        {
          // If there's 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 hasn't yet expired. Throw a CircuitBreakerOpen exception to
      // inform the caller that the call was not actually attempted,
      // and return the most recent exception received.
      throw new CircuitBreakerOpenException(stateStore.LastException);
    }
    ...

Para usar un objeto CircuitBreaker para proteger una operación, una aplicación crea una instancia de la clase CircuitBreaker e invoca el método ExecuteAction, lo que especifica que la operación debe realizarse como el parámetro.To use a CircuitBreaker object to protect an operation, an application creates an instance of the CircuitBreaker class and invokes the ExecuteAction method, specifying the operation to be performed as the parameter. La aplicación debe estar preparada para detectar la excepción CircuitBreakerOpenException, si se produce un error en la operación porque el disyuntor está abierto.The application should be prepared to catch the CircuitBreakerOpenException exception if the operation fails because the circuit breaker is open. El código siguiente muestra un ejemplo:The following code shows an example:

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)
{
  ...
}

Los patrones siguientes también pueden ser útiles a la hora de implementar este modelo:The following patterns might also be useful when implementing this pattern:

  • Patrón Retry.Retry pattern. Describe el modo en que una aplicación puede tratar los errores temporales anticipados cuando intenta conectarse a un servicio o un recurso de red, al reintentar de forma transparente una operación que anteriormente fracasó.Describes how an application can handle anticipated temporary failures when it tries to connect to a service or network resource by transparently retrying an operation that has previously failed.

  • Patrón Health Endpoint Monitoring.Health Endpoint Monitoring pattern. Un disyuntor puede probar el estado de un servicio enviando una solicitud a un punto de conexión expuesto por el servicio.A circuit breaker might be able to test the health of a service by sending a request to an endpoint exposed by the service. El servicio debería devolver información que indique su estado.The service should return information indicating its status.