Saltar al contenido

Patrón circuit breaker: estados, umbrales, reintentos y fallbacks

Cómo un circuit breaker detiene los fallos en cascada: estados cerrado, abierto y semiabierto, umbrales realistas, timeouts, reintentos y buenos fallbacks.

· Fundador y desarrollador

Publicado el

Diagrama de estados con tres estados. Closed pasa a Open cuando la tasa de fallos llega al 50 % de las últimas 100 llamadas. Open pasa a Half-open tras 60 segundos. Half-open vuelve a Closed si las llamadas de prueba salen bien, o a Open si una falla. Debajo hay un camino de llamadas.

Un proveedor de pagos empieza a responder en diez segundos en lugar de en cincuenta milisegundos. Tu servicio de checkout lo sigue llamando, cada petición retiene un worker mientras espera y, en un minuto, el checkout no tiene workers libres, ni siquiera para peticiones que nunca necesitaron al proveedor de pagos. Después, los servicios que llaman al checkout también empiezan a agotar sus timeouts. Una sola dependencia lenta se ha convertido en una caída que afecta a varios equipos.

Un circuit breaker corta esa cadena negándose a llamar, durante un tiempo, a una dependencia que claramente está fallando y respondiendo de inmediato en su lugar. Este artículo cubre los tres estados con un diagrama de estados, los umbrales que deciden cuándo salta el breaker, cómo se combina con timeouts, reintentos y bulkheads, qué hace bueno a un fallback y dónde poner el breaker: en tu código o en un service mesh.

Por qué los fallos se propagan en cascada

La aritmética es sencilla e implacable. Un servicio con 200 hilos de trabajo que llama a una dependencia que responde en 50 ms puede atender unas 4000 peticiones por segundo por ese camino. Si la dependencia se ralentiza hasta un timeout de 10 segundos, los mismos 200 hilos atienden 20 peticiones por segundo. En tu servicio no hay nada roto; su capacidad simplemente se ha consumido esperando.

Los reintentos lo empeoran. Si tres capas de servicios reintentan tres veces cada una, una sola petición de usuario puede convertirse en 27 llamadas a la dependencia en apuros del fondo, justo en el momento en que menos puede permitírselas.

Michael Nygard describió el circuit breaker en 2007 en su libro sobre cómo construir software para producción, y la descripción de Martin Fowler lo convirtió en una pieza estándar del diseño de servicios: envolver las llamadas a una dependencia, contar los fallos y, cuando superan un umbral, dejar de llamar durante un tiempo.

Los tres estados

Estado Qué pasa con las llamadas Pasa a
Cerrado (Closed) Las llamadas pasan; los resultados se registran en una ventana deslizante Abierto, cuando la tasa de fallos o de llamadas lentas supera el umbral
Abierto (Open) Las llamadas fallan de inmediato sin tocar la dependencia; responde el fallback Semiabierto, tras un tiempo de espera
Semiabierto (Half-open) Pasa un pequeño número de llamadas de prueba; el resto sigue fallando de inmediato Cerrado si las pruebas salen bien, abierto si fallan

El estado abierto hace dos trabajos a la vez. Protege tu servicio, porque fallar rápido libera los workers al instante. También protege la dependencia, que obtiene un respiro del tráfico mientras se recupera, en lugar de una pila creciente de reintentos.

El estado semiabierto existe porque el breaker no puede saber si la dependencia vuelve a estar sana sin probarlo. Dejar pasar un puñado de llamadas, no todo el tráfico, comprueba la recuperación sin tumbar la dependencia justo cuando vuelve.

Qué cuenta como fallo

Decídelo de forma deliberada, porque una mala elección abre los breakers por motivos equivocados:

  • Cuenta: timeouts, errores de conexión, respuestas 5xx y llamadas más lentas que tu umbral de llamada lenta.
  • No cuentes: respuestas 4xx como 400 o 404. Describen una petición incorrecta, no una dependencia que falla, y un único cliente que se porta mal no debería dejar sin servicio a todos.
  • Decide caso por caso: 429 Too Many Requests. Significa que la dependencia se está protegiendo, lo que a menudo es un buen motivo para retroceder.

Las llamadas lentas importan tanto como los errores. Una dependencia que responde bien a todas las peticiones en ocho segundos es, para una API interactiva, una dependencia que falla.

Elegir los umbrales

Resilience4j, una biblioteca de breakers muy usada en la JVM, trae unos valores por defecto conservadores que conviene conocer, porque muchos equipos nunca los cambian:

Ajuste Por defecto Qué significa
failureRateThreshold 50 % Abrir cuando la mitad de las llamadas registradas han fallado
slidingWindowType / slidingWindowSize Por número, 100 Juzgar las últimas 100 llamadas
minimumNumberOfCalls 100 No juzgar hasta tener 100 llamadas registradas
slowCallDurationThreshold 60 000 ms Las llamadas más lentas cuentan como lentas
slowCallRateThreshold 100 % Abrir solo si todas las llamadas son lentas
waitDurationInOpenState 60 000 ms Permanecer abierto un minuto
permittedNumberOfCallsInHalfOpenState 10 Diez llamadas de prueba deciden la recuperación

Para una API interactiva, los valores por defecto de llamadas lentas equivalen a tenerlas desactivadas: nadie espera 60 segundos una respuesta. Unos puntos de partida razonables son un umbral de llamada lenta cercano a tu objetivo de latencia, una tasa de llamadas lentas del 50 % al 80 % y una espera en estado abierto de 10 a 30 segundos. Mantén minimumNumberOfCalls con sentido: en un endpoint con poco tráfico, dos fallos de tres llamadas son ruido, no una caída. Las ventanas por tiempo se comportan mejor que las de número cuando el tráfico varía mucho a lo largo del día.

Timeouts, reintentos y bulkheads

Un circuit breaker es una de cuatro herramientas que solo funcionan bien juntas:

  • Los timeouts van primero. Sin timeout, una llamada lenta nunca falla, así que el breaker no ve nada que contar. Pon cada timeout por debajo del timeout del llamante y propaga los plazos por la cadena de llamadas para que nadie siga trabajando en una petición ya abandonada.
  • Los reintentos necesitan límites. Reintenta solo operaciones idempotentes, usa backoff exponencial con jitter como se describe en el AWS Architecture Blog, reintenta en una sola capa y no en todas, y limita los reintentos a una parte del tráfico. Los presupuestos de reintentos de Envoy son por defecto el 20 % de las peticiones activas cuando los activas.
  • Nunca reintentes contra un breaker abierto. Resilience4j aplica por defecto el retry por fuera del breaker, así que cada intento pasa por él; configura los reintentos para que se detengan con el error «call not permitted» en lugar de machacar un breaker que ya ha decidido.
  • Los bulkheads aíslan. Da a cada dependencia su propio pool de conexiones o su propio límite de concurrencia, para que un proveedor de pagos lento solo pueda consumir su pool y no todos los workers del servicio.

Fallbacks con sentido

Cuando el breaker está abierto, algo tiene que responder. El fallback adecuado depende de para qué sirvan los datos:

Funcionalidad Buen fallback Mal fallback
Recomendaciones Artículos populares, u ocultar la sección Reintentar el servicio de recomendaciones en bucle
Avatar del usuario Una imagen genérica Bloquear la página hasta que responda el servicio de imágenes
Estimación de envío Una estimación en caché con una nota Una fecha de entrega inventada
Precio o pago Un error claro y una forma de volver a intentarlo Un precio adivinado o un cobro omitido en silencio

Un fallback debe ser barato y no puede depender de lo que acaba de fallar. Si la respuesta honesta es «esto no funciona ahora mismo», falla rápido con un error claro; sigue siendo mucho mejor que un timeout.

Dónde poner el breaker

Biblioteca en el proceso Service mesh o proxy
Ejemplos Resilience4j (Java), Polly (.NET), gobreaker (Go), opossum (Node.js) Circuit breaking y outlier detection de Envoy
Ve Excepciones, fallos de negocio, fallbacks Conexiones, peticiones, códigos de respuesta
Fallbacks Sí, en tu código No; el llamante recibe un error
Coherencia entre equipos Cada equipo configura el suyo Una sola política, aplicada en todas partes

Merece la pena entender el modelo de Envoy, porque difiere del de los libros. Sus circuit breakers son límites de concurrencia por clúster upstream: por defecto 1024 conexiones, 1024 peticiones pendientes, 1024 peticiones activas y 3 reintentos simultáneos. La detección de fallos por host es una función aparte, la outlier detection, que por defecto expulsa un host tras cinco respuestas 5xx consecutivas durante 30 segundos, más tiempo si se repite la expulsión, y nunca expulsa más del 10 % de un clúster. El diagrama de arquitectura de service mesh muestra dónde se sitúan estos proxies.

Muchos equipos usan ambos: el mesh para tener límites coherentes y expulsar hosts defectuosos, y una biblioteca donde la aplicación necesita un fallback de verdad. Hystrix, de Netflix, que popularizó el patrón, está en modo mantenimiento desde 2018; Resilience4j es su sustituto habitual en la JVM.

Obsérvalo y pruébalo

Un breaker que se abre en silencio convierte una caída en un misterio. Emite cada cambio de estado como evento de log y como métrica, alerta cuando un breaker se queda abierto y muestra el estado de los breakers en el mismo dashboard que la latencia de la dependencia. Después pruébalo: inyecta latencia y errores en la dependencia en un entorno de staging y observa cómo se abre el breaker, responde el fallback y el breaker vuelve a cerrarse. El diagrama de arquitectura de observabilidad cubre la parte de métricas y trazas.

El diagrama del circuit breaker

Diagrama de estados con tres estados. Closed pasa a Open cuando la tasa de fallos llega al 50 % de las últimas 100 llamadas. Open pasa a Half-open tras 60 segundos. Half-open vuelve a Closed si las llamadas de prueba salen bien, o a Open si una falla. Debajo hay un camino de llamadas.

11 componentes · 7 conexiones

Abrir en Archboard
Contenido del diagrama
  • CLOSED
  • OPEN
  • HALF-OPEN
  • Checkout service (caller)
  • Payment provider
  • Fallback: clear error, retry later
  • failure rate ≥ 50% of the last 100 calls
  • after a 60 s wait
  • a trial call fails
  • trial calls succeed
  • charge(), 2 s timeout, when closed or half-open
  • when open: no call, fail fast
Un circuit breaker como máquina de estados. Cerrado deja pasar las llamadas y mide los fallos; abierto falla de inmediato y sirve un fallback; semiabierto deja pasar unas pocas llamadas de prueba para comprobar si la dependencia se ha recuperado.

Cómo leerlo:

  • Tres estados. Cerrado es el estado normal, abierto falla rápido y semiabierto comprueba la recuperación. El punto marca el estado inicial.
  • Las transiciones llevan sus condiciones: una tasa de fallos del 50 % en las últimas 100 llamadas abre el breaker, una espera de 60 segundos lo pasa a semiabierto y el resultado de las llamadas de prueba lo cierra o lo vuelve a abrir.
  • El camino de llamadas de abajo muestra al llamante, al proveedor de pagos detrás del breaker y el fallback que responde mientras el breaker está abierto.
  • El bloque Markdown enumera los ajustes con sus valores por defecto.

Dibujar con claridad los patrones de resiliencia

  • Dibuja el breaker como una máquina de estados. Las descripciones en prosa difuminan la diferencia entre abierto y semiabierto; un diagrama la hace evidente.
  • Escribe los números en las transiciones. «Se abre con fallos» invita a discutir; «se abre al 50 % de las últimas 100 llamadas» invita a revisar.
  • Muestra el camino del fallback, incluido lo que ve el usuario.
  • Pon los timeouts y los reintentos en el diagrama del camino de llamadas, para que los revisores puedan multiplicar los reintentos entre capas.

Para más detalles sobre la notación de máquinas de estados, consulta el ejemplo de diagrama de estados. El diagrama de arquitectura de microservicios muestra dónde se sitúan los breakers entre servicios.

Constrúyelo en Archboard

  1. Inicia sesión con Google o GitHub y crea un diagrama.
  2. Añade tres nodos Estado del pack UML: Closed, Open y Half-open. Coloréalos en verde, rojo y ámbar.
  3. Añade una pequeña forma Elipse del pack Básico como estado inicial y conéctala a Closed.
  4. Dibuja las transiciones como aristas etiquetadas: el umbral de fallos, el tiempo de espera, el éxito de las pruebas y su fallo. Añade junto a cada estado un Texto del pack Básico que diga qué pasa allí con las llamadas.
  5. Debajo de la máquina de estados, añade una Caja del pack Básico para el llamante, un Sistema externo del pack Modelo C4 para el proveedor de pagos y otra Caja para el fallback, con una arista discontinua para el camino del estado abierto.
  6. Añade un bloque Markdown del pack Básico con los ajustes y sus valores por defecto.

El ejemplo usa 8 nodos de diagrama, dentro del plan Free.

Preguntas frecuentes

¿Cuáles son los tres estados de un circuit breaker? Cerrado, en el que las llamadas pasan y se cuentan los fallos; abierto, en el que las llamadas fallan de inmediato durante un tiempo de espera; y semiabierto, en el que unas pocas llamadas de prueba deciden si el breaker se vuelve a cerrar o se vuelve a abrir.

¿Qué diferencia hay entre un circuit breaker y un reintento? Un reintento repite una llamada fallida con la esperanza de que salga bien. Un circuit breaker detiene por completo las llamadas cuando los fallos son frecuentes. Si se usan juntos, los reintentos deben estar limitados y detenerse cuando el breaker está abierto.

¿Los errores 4xx deberían abrir un circuit breaker? Normalmente no. Un 400 o un 404 significa que la petición era incorrecta, no que la dependencia haya fallado. Cuenta en su lugar los timeouts, los errores de conexión, las respuestas 5xx y las llamadas lentas.

¿Necesito un circuit breaker si uso un service mesh? Un mesh te da límites de concurrencia y la expulsión de hosts defectuosos sin cambiar el código. Aun así necesitas un breaker en el proceso allí donde quieras un fallback de verdad, como un valor en caché o una funcionalidad degradada.

¿Cuál es un buen tiempo de espera para el estado abierto? Lo bastante largo para que una dependencia en apuros se recupere y lo bastante corto para que los usuarios no sufran una caída larga después de que lo haga. De diez a treinta segundos es un punto de partida habitual para las APIs interactivas.

Empieza gratis — un puesto, sin tarjeta de crédito.

Artículos relacionados

← Volver al blog

Abre un lienzo en un minuto

Inicia sesión con Google o GitHub. Gratis para un asiento. Mejora el plan cuando se una el equipo.