Table of Contents
Introducción: El desafío de la resiliencia en la computación sin servidores
El computador sin servidor ha reencarnado cómo los desarrolladores construyen y implementan aplicaciones mediante la gestión de infraestructuras abstractas y la ampliación automática. Plataformas como AWS Lambda, Azure Funciones y Google Cloud Funciones permiten a los equipos enfocarse en la lógica de negocio mientras pagan sólo por uso real. Sin embargo, este modelo introduce problemas de resistencia únicos.
Un interruptor Breaker actúa como una válvula de seguridad para su aplicación. Monitoriza llamadas a servicios remotos o recursos y evita nuevos intentos cuando las tasas de fallo superan un umbral. Esto protege al sistema de ser abrumado, permite que los servicios de no poder recuperarse, y proporciona una recuperación limpia para los usuarios. En este artículo, ampliamos el contenido original para darle una guía integral y accionable para implementar interruptores en aplicaciones sin servidor, incluyendo explicaciones detalladas, prácticas de plataforma.
Comprender el patrón de interruptor de profundidad en profundidad
El patrón de interruptor de interruptor fue popularizado por Michael Nygard en su libro ¡Desarrollar!] y posteriormente formalizado en patrones de nublación. Se comporta como un interruptor eléctrico: cuando un circuito detecta una falla (por ejemplo, un corto), abre y detiene el flujo de corriente. En software, los estados del circuito son:
- ]Cerrado:] Pide flujo normalmente al servicio de corriente baja. El interruptor monitorea las tasas de falla (por ejemplo, errores HTTP 5xx, timeouts). Si los fallos superan un umbral configurado dentro de una ventana de tiempo determinada (por ejemplo, 10 fallos en 30 segundos), los viajes de circuito a Abrir
- Abierto:] Las solicitudes son rechazadas inmediatamente (o se invoca la lógica de retroceso) sin llamar al servicio de desperdicio. Esto evita que los recursos desperdiciados y da al servicio de corriente baja una ventana de recuperación. Después de un período de tiempo (por ejemplo, 30 segundos), el circuito transiciones a .
- ]Half-Open: Se permite un número limitado de solicitudes de prueba. Si estos éxitos (dentro de criterios de éxito definidos), el circuito se reasienta a Closed. Si los fallos persisten, vuelve a y el tiempo de salida se reasienta o aumenta.
Esta máquina estatal es crítica. Sin ella, un breve paseo podría causar que todos los clientes vuelvan a entrar simultáneamente, creando un rebaño que extiende la salida. El patrón de Circuit Breaker también proporciona retroalimentación temprana de fallos a los clientes, permitiendo la degradación agraciada, por ejemplo, el retorno de datos en caché o un mensaje de error amistoso en lugar de un tiempo.
El artículo seminal de Martin Fowler sobre el interruptor de circuito sigue siendo la referencia fundamental. Explica cómo el patrón se integra con otros patrones de resistencia como Retry y Bulkhead.
Parámetros clave para el ajuste
Cada implementación de interruptores expone parámetros configurables que deben ajustarse al comportamiento de su aplicación:
- umbral de falla: Número de fallos consecutivos (o tasa por ventana) para abrir el circuito.
- Duración del tiempo: Cuánto tiempo permanece abierto el circuito antes de pasar a mitad abierto.
- Cuenta de prueba abierta: Número de solicitudes exitosas requeridas para cerrar el circuito.
- Clasificación del espejo: ¿Qué respuestas cuentan como fallos? ¿Sólo 5xx? ¿Tiempos? 4xx? (Usualmente sólo errores del lado del servidor).
- Recuperación del tiempo: Opcionalmente incremental (retrocedimiento exponencial) para evitar el rebote.
Las aplicaciones sin servidor añaden complejidad: porque las funciones son efímeros, no puede confiar en el estado de memoria para el circuito. Si un caso Lambda falla, el estado del circuito puede perderse. Por lo tanto, el almacenamiento externo del estado (DynamoDB, Redis o un servicio gestionado) es a menudo necesario.
Implementación de interruptores en entornos sin servidor
Implementar un interruptor en una arquitectura sin servidor requiere adaptar el patrón a las limitaciones de la plataforma. Cubriremos tres enfoques principales: usando funciones de API gestionadas, aprovechando bibliotecas de terceros dentro de su código de función, y empleando servicios de orquestación como AWS Step Functions.
Enfoque 1: API de la red de circuitos y de la apertura de circuitos
AWS API Gateway puede actuar como un interruptor rudimentario mediante la resolución de solicitudes a una función backend Lambda. Cuando la función devuelve demasiados errores de 5xx o supera los límites de concurrencia, API Gateway puede configurarse para devolver una respuesta de descomposición (por ejemplo, un mensaje estático de un autor de la aduana o respuesta de integración).
Ejemplo: Establecer un plan de uso de la API Gateway con un límite de ráfaga y un límite de velocidad que refleje la capacidad de su backend. Cuando la función Lambda está abrumada, API Gateway responde con inmediatamente, actuando como un interruptor de ida. Pero esto no distingue entre el pulverización y las fallas reales de servicio.
Enfoque 2: Interruptores de circuito de movimiento con bibliotecas
El enfoque más flexible es incrustar una biblioteca de interruptores dentro de sus funciones Lambda. Debido a que las funciones Lambda son apátridas y escaladas horizontalmente, el estado de interruptor debe almacenarse externamente para que cada invocación pueda comprobar el estado actual. Un patrón común utiliza Amazon DynamoDB] (o Redis with ElastiCache) para persistir el circuito en todo el estado.
Para Node.js, la Opossum] biblioteca es un interruptor de uso muy amplio. Admite funciones de retroceso, tiempo de salida y umbral de volumen. Aquí está una implementación simplificada adaptada para AWS Lambda:
const CircuitBreaker = require('opossum');
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();
const circuitBreakerState = {
state: 'CLOSED',
failureCount: 0,
lastFailureTime: null
};
// Persist state in DynamoDB after each transition
async function persistState(newState) {
await dynamo.put({
TableName: 'CircuitBreakerState',
Item: { serviceId: 'payment-service', ...newState }
}).promise();
}
async function loadState() {
const data = await dynamo.get({
TableName: 'CircuitBreakerState',
Key: { serviceId: 'payment-service' }
}).promise();
return data.Item || circuitBreakerState;
}
// The actual downstream call
async function callPaymentService(payload) {
const http = require('axios');
const response = await http.post('https://payment.example.com/charge', payload);
return response.data;
}
// Circuit breaker options
const options = {
errorThresholdPercentage: 50,
resetTimeout: 30000,
volumeThreshold: 10
};
// Create breaker with external state integration (simplified)
const breaker = new CircuitBreaker(callPaymentService, options);
breaker.fallback(() => ({ error: 'Payment service unavailable, order processed in offline mode' }));
exports.handler = async (event) => {
// Load state from DynamoDB and update breaker
const savedState = await loadState();
// Opossum doesn't natively restore state; you'd need to implement a wrapper.
// For brevity, assume the breaker is fresh per function invocation but uses external checks.
// In production, use a shared cache with TTL instead of per-invocation state load.
return breaker.fire(event.body);
};
Este ejemplo omite la integración completa para la claridad. En la práctica, usted necesita sincronizar el estado de interruptor en muchas invocaciones de funciones simultáneas usando escritos condicionales en DynamoDB (preparación optimizada) para evitar las condiciones de carrera. Para escenarios de alto rendimiento, una instancia Redis (por ejemplo, usando ElastiCache Serverless) es a menudo más performant.
Enfoque 3: Funciones de paso AWS – Interruptor de circuitos de apertura-velocidad
Para flujos de trabajo multi-pasos (por ejemplo, checkout de comercio electrónico), AWS Step Functions puede modelar un interruptor como máquina estatal. El estado puede comprobar un contador o una bandera almacenada en una mesa DynamoDB. Si el recuento de fallos excede un umbral, el flujo de trabajo se redirige a una ruta de retroceso (por ejemplo, enviar un administrador, cola para el procesamiento manual).
Ejemplo: Una función de paso que llama a dos servicios de aguas abajo. Después de un fallo, aumenta un contador DynamoDB. Antes de cada invocación posterior, la función de paso lee el contador. Si supera 5, el flujo de trabajo toma inmediatamente el camino de retorno. Esto es efectivamente un interruptor de circuito a nivel de flujo de trabajo.
Desafíos y soluciones sin servidor
- Cold comienza:] El estado de interruptor necesita sobrevivir el reciclaje de instancias de función. Usar el estado externo con un TTL corto para cerrar automáticamente el circuito después de un período de no actividad.
- Concurrencia: Muchas instancias de función pueden comprobar y actualizar el estado simultáneamente. Use bloqueo optimista (expresiones de estado DynamoDB) o patrones de aumento/dedicación atómica.
- Cost:] Cada cheque del estado añade costos de lectura/escritura. Estado de la caché en memoria con una caducidad corta (por ejemplo, 1 segundo) dentro de la misma instancia de función para reducir las lecturas de DynamoDB, pero aceptar eventual consistencia.
- Fácilidad de tiempo: Las funciones de lambda tienen un tiempo máximo de invocación (15 minutos). Los horarios de interruptores deben ser mucho más cortos (segundos) para evitar mantener abierta la función.
Beneficios de usar los interruptores
Las ventajas se extienden mucho más allá de lo básico. Vamos a explorar cada beneficio en un contexto sin servidor:
Resiliencia mejorada – Prevención de las fallas de caducidad
Si el servicio A llama B y B llama C y C falla, el fallo se propaga inmediatamente. Un interruptor de la llamada de B a C hará que B abra su circuito después de algunos fallos. Ahora, las solicitudes de A a B son rechazadas inmediatamente con un retroceso, evitando que B agote su límite de concurrencia y se convierta en un embotellado. Esto aisla la culpa a su origen.
Recuperación más rápida – Auto-sanación sin intervención manual
Cuando un circuito está abierto, el servicio de fallos recibe un período de descanso. No se envían solicitudes, lo que le permite recuperar (por ejemplo, reiniciar, limpiar una fuga de memoria o reconfigurar).El estado medio abierto probe periódicamente el servicio. Una vez que responde correctamente, el circuito se cierra automáticamente. Este auto-sanamiento es vital para los sin servidor donde las funciones de depuración en vivo es difícil.
Experiencia de usuario mejorada – Degradación grata
En lugar de mostrar una página genérica de “Server Error” o una cargadora de spinning, puede devolver datos de establo, una versión simplificada de la función o un mensaje amistoso. Por ejemplo, un servicio de recomendación de producto puede usar un interruptor: cuando está abierto, la página de producto muestra “Recomendaciones temporalmente indisponibles” en lugar de fallar por completo.
Ahorros de costes – Evitar invocaciones innecesarias
El precio sin servidor se basa en solicitudes y duración. Cuando un servicio de corriente baja está fallando, continuando llamándolo gasta dinero. Cada invocación de su función que inmediatamente falla (o que resulta en un tiempo de espera para el flujo de entrada) todavía cuesta. Un interruptor detiene estas llamadas, reduciendo costos durante las ventanas de falla.
Mejores prácticas para desplegar los interruptores
La implementación de un interruptor no es una actividad única que se adapta a todas las actividades. Use estas prácticas para maximizar la eficacia en un entorno sin servidor.
Establecer los puntos de falla apropiados y los tiempos de salida
Por ejemplo, si su servicio de corriente baja tiene como objetivo el 99,9% de tiempo de inactividad, un umbral de 5 fallos por minuto puede ser demasiado sensible (puede abrirse durante los blips menores). Comience con un umbral más alto (por ejemplo, un 20% de tasa de error sobre una ventana de 1 minuto) y ajuste utilizando datos de monitoreo. Los plazos deben ser ligeramente más largos que el tiempo de respuesta típico del servicio de incipiente, pero más corto que el tiempo total de sus funciones.
Implement Fallback Mechanisms
Cada solicitud de circuito abierto debe tener un retroceso.
- Recuperar datos de caché (de ElastiCache, CloudFront o una base de datos).
- Realizar la solicitud de procesamiento posterior (por ejemplo, SQS DLQ).
- Devuelve un valor predeterminado o una respuesta estática.
- Redirecta a una versión degradada de la característica (por ejemplo, personalización deshabilitada).
Los inconvenientes deben ser idempotentes cuando sea posible, especialmente para los escritos.
Monitor and Log Circuit States
Instruya su interruptor para registrar cada cambio de estado y la métrica de fallos. Use CloudWatch Metrics (por ejemplo, métricas personalizadas para el conteo abierto de circuitos, pruebas de media apertura, uso de la devolución). Establecer alarmas: si un circuito permanece abierto para un período prolongado, notificar operaciones. También inicie sesión la razón de fracaso – timeout, código de error, etc. – para ayudar a depurar.
Combina con otros patrones de resiliencia
- Retry: Usar un patrón de retry en el lado el interruptor, pero con retroceso y desprendimiento exponencial. El interruptor en sí mismo no debe reiniciar; en cambio, la llamada del cliente se envuelve con una política de reentrada (por ejemplo, los retratamientos de circuito incorporados AWS SDK).
- ]Bulkhead:] Aisla los recursos por función o servicio. Por ejemplo, reserve un límite de concurrencia para funciones críticas vs. no críticas. Cuando se abre un circuito, reduce la carga en el servicio de falla, impidiendo que afecte a otras partes del sistema.
- Tiempo:] Siempre se establece un tiempo de duración en llamadas de abajo – más corto que la ventana de falla del interruptor. Esto evita que las solicitudes de suspensión de largo tiempo se desvian.
- Puntos finales de comprobación de salud: Usar un proceso de fondo (por ejemplo, un evento programado CloudWatch) para invocar periódicamente un punto final de comprobación de salud. Si el cheque de salud falla, abra el circuito de forma preventiva antes de que los usuarios se vean afectados.
Prueba tu interruptor de interruptor bajo falla
La ingeniería de caos es tu amigo. Usa herramientas como AWS Fault Injection Simulator (FIS) para inyectar fallos en tus servicios de aguas abajo y observar el comportamiento del interruptor. Verificar que:
- El circuito se abre dentro del plazo previsto.
- Fallbacks ejecutan correctamente.
- El circuito se recupera (half-open y luego se cierra) después de que la falla se resuelva.
- No se producen falsos positivos bajo los picos normales de carga.
Pruebas en un entorno de estadificación que refleja la producción es esencial. Documenta el comportamiento esperado y ejecute ejercicios regularmente.
Use una tienda de estado externo con TTL
En el caso de los servidores, no puede confiar en la memoria local a través de invocaciones. Use DynamoDB, ElastiCache for Redis, o un servicio hecho como Eureka. Establezca un tiempo a vivo (TTL) en el registro estatal para que si su función está inactiva durante un largo período, el circuito se reinicia automáticamente para cerrar. Esto evita que un estado abierto de bloqueo del tráfico después de un servicio se ha recuperado.
Conclusión
Como el servidor se convierte en la columna vertebral de las aplicaciones modernas, los patrones de resiliencia como el interruptor Breaker ya no son opcionales, son esenciales para el control de costos, tiempo de funcionamiento y satisfacción del usuario. Al entender la máquina del estado, implementarlo correctamente dentro de las limitaciones de su plataforma sin servidor (API Gateway, código de funciones o Funciones de Paso), y siguiendo las mejores prácticas para el monitoreo y la prueba, puede construir sistemas que se degradan con gracia en el fallo y recuperar sin intervención manual.
El patrón de interruptores es sólo una pieza del rompecabezas de la resiliencia. Combinarlo con las retries, mamparos, controles de salud, y la observabilidad integral para crear arquitecturas sin servidor verdaderamente robustas.