El Patrón Disyuntor (Circuit Breaker)

Si la semana pasada hablaba de que era importante tener sistemas estables para dormir tranquilos, hoy quiero mostrar una técnica no demasiado conocida pero bastante efectiva para aumentar la estabilidad de las aplicaciones: los disyuntores (circuit breakers).

Disyuntor

Un disyuntor es un mecanismo capaz de abrir o cerrar un circuito eléctrico bajo determinadas circunstancias. Es lo que en España se conoce habitualmente como “automático” y es lo que “salta” cuando hay algún problema en la red eléctrica, por ejemplo cuando se intenta usar más potencia de la contratada o se conecta algún electrodoméstico defectuoso. Cuando “salta el automático”, se fuerza la apertura del circuito y se evita que los problemas se propaguen.

Cómo aplicar la idea al desarrollo de software

Cuando tenemos una aplicación que se integra con sistemas externos, estamos expuestos a que los fallos se propaguen de unos sistemas a otros, provocando reacciones en cadena cuyas consecuencias pueden ser bastante graves. Un disyuntor nos ayuda a evitar la propagación de errores de un sistema a otro, deteniendo así la reacción en cadena.

Un caso de uso típico para un disyuntor es controlar el acceso a un servidor externo.

Supongamos que tenemos un servidor que empieza a responder más despacio a las peticiones de los clientes. Al tardar más en atender cada petición, existe el riesgo de que se vayan acumulando peticiones, cada una de ellas introduciendo más carga en el servidor y amplificando el problema. Llegado un momento, el servidor puede llegar a caer por completo.

Desde el punto de vista del cliente, la situación no es mucho mejor. El servicio que recibe se va degradando paulatinamente hasta que se interrumpe totalmente. Además, se están consumiendo inutilmente recursos locales y esos recursos pueden ser limitados, como por ejemplo la batería en un teléfono móvil que se conecta a internet.

Funcionamiento de un Disyuntor

Un disyuntor nos permite minimizar problemas como el que acabo de contar. La forma más sencilla de entender un disyuntor es verlo como una máquina de estados:

Diagrama de esatdo de un disyuntor

  • Cerrado: inicialmente, el disyuntor mantiene el circuito cerrado y permite que se realicen las operaciones, es decir, que se invoque al recurso externo. Cuando se producen N errores seguidos, se considera que el recurso externo no está accesible y se abre el circuito.
  • Abierto: cuando el circuito está abierto, las llamadas al sistema remoto no se realizan, sino que fallan directamente. De esta forma se evita seguir cargando el sistema remoto y se evitan también costes locales (por ejemplo, el tiempo de espera antes de decidir que el sistema remoto no responde). Tras un tiempo prudencial, se pasa al estado Semi Abierto.
  • Semi Abierto: en el estado Semi Abierto, la siguiente solicitud que se realice se enviará al sistema remoto para comprobar si ha vuelto a la vida. Si es así, el disyuntor pasa a estado Cerrado y vuelve a funcionar normalmente. Si no, vuelve a estado Abierto, iniciándose un nuevo periodo de espera.

Cómo implementarlo

La implementación de esto no es complicada y hay algunos ejemplos online de cómo implementar un disyuntor en C#. A la hora de implementarlo hay que tener en cuenta que normalmente el recurso externo que se está protegiendo con el disyuntor suele ser accedido desde varias hebras en el cliente, por lo que la implementación del disyuntor debe ser thread safe.

Normalmente se suele crear una implementación genérica, creando una clase que reciba un Action o un Func<T> e incorpore la lógica de apertura y cierre de circuito, pudiendo ser reutilizada con distintos servicios:

var service = new MyRemoteServiceProxy();
var breaker = new CircuitBreaker
{
    Timeout = TimeSpan.FromMinutes(2),
    ErrorThreshold = 5
};
var data = breaker.Get(() => service.GetDataFromRemoteServer());

Otra alternativa es utilizar decoradores sobre las clases que acceden a los servicios externos, o incluso emplear técnicas de AOP con interceptores como los que vimos hace poco.

En cualquier caso, lo recomendable es siempre aislar la lógica propia del disyuntor de la lógica de invocación al servicio que estamos controlando.

Conclusiones

Como todos los patrones de diseño, el disyuntor no es más que la formalización de soluciones aplicadas frecuentemente durante el desarrollo y seguramente lo hayas aplicado más de una vez sin darle nombre. La ventaja de formalizarlo es que permite reutilizar la solución en distintos contextos.

Es una técnica interesante y bonita por su simplicidad, y su uso nos permite proteger un sistema de fallos en sus colaboradores mejorando la estabilidad global.

Tener un sistema sólido no depende de un único factor, pero es la suma de muchos pequeños factores cómo este (y algunos no tan pequeños) la que hará que todo funcione o se caiga a las primeras de cambio.

7 comentarios en “El Patrón Disyuntor (Circuit Breaker)

  1. José Manuel dijo:

    Siempre temas de los que se puede sacar mucha sabiduría y buen hacer. Gracias por el artículo, Juanma.

  2. La verdad es que no sabía nada de este patrón. Acabo de conocer tu web por el tema de los artículos relacionados con buenas prácticas en los junits.
    Y ahora me encuentro con este estupendo artículo.
    Muchas gracias.

  3. Gracias por tus comentarios, Joaquín.

    Lo cierto es que no es un patrón muy conocido (y tampoco muy usado), pero es curioso y hay situaciones en las que puede venir bien.

  4. Estos días he tenido que implementar una llamada a un servicio web y he podido aplicar el patrón. El problema es que se trata de una página WebForms de ASP.Net y, al no tener estado, no se mantiene el estado del circuit breaker. He probado a guardarlo en Application y ahí funciona perfectamente, aunque no sé si es una solución óptima :/

    Muchas gracias por el artículo.

    También hay un paquete de NuGet que implementa el patrón.

  5. Muchas gracias por el artículo. Me gustaría ver algún ejemplo real con WebForms de ASP.Net, por lo que comentaba Roger del estado, y también como dice el artículo «thread-safe».
    Saludos.

  6. Hola miriam,

    No entiendo muy bien qué pinta WebForms en esta historia, la verdad. Si desde un WebForm accedes a un recurso que puede «caerse», puedes encapsular el acceso a través de un CircuitBreaker.

    Si te preocupa mantener el estado del CircuitBreaker, hay muchas formas de hacerlo. Puedes declararlo como una variable estática, usar un Singleton, resolverlo de un contenedor de inversión de control que regule su ciclo de vida o incluso persistir el estado (abierto/cerrado/semi y número de fallos) en disco o una base de datos.

    Un saludo,

    Juanma

Comentarios cerrados.