Un balanceador selecciona un destino para una conexión o petición. Permite repartir tráfico entre instancias y retirar destinos que no pueden atenderlo, pero introduce su propia capacidad, configuración y modo de fallo. El objetivo es conservar un servicio útil cuando cambian la carga y los servidores disponibles.
El diseño comienza con la unidad que se distribuye, el trabajo que cuesta atenderla y el comportamiento admitido durante un cambio de destino. Dos peticiones pueden tener costes muy distintos aunque ambas cuenten como una.
L4 y L7
Un balanceador L4 toma decisiones con información de transporte, como direcciones, puertos y flujos TCP. Normalmente asigna una conexión o flujo a un destino; las peticiones que viajen por una conexión persistente pueden seguir juntas. El reparto de conexiones no implica un reparto uniforme de trabajo.
Un balanceador L7 entiende un protocolo de aplicación, por ejemplo HTTP, y puede enrutar según host, ruta o cabeceras. Puede terminar TLS y seleccionar destinos por petición cuando el producto y el protocolo lo permiten. Esto añade procesamiento y exige definir dónde vuelve a cifrarse el tráfico y qué cabeceras son confiables. Las capacidades exactas dependen del servicio elegido.
La decisión se conecta con el contrato de la API: timeouts, límites de tamaño, streams y respuestas largas afectan cuánto tiempo ocupa recursos una petición. WebSockets, conexiones persistentes y streams necesitan una política explícita durante despliegues.
Selección y afinidad
Round robin distribuye turnos. Funciona mejor cuando los destinos y sus peticiones tienen costes comparables. Weighted round robin permite representar capacidades diferentes, pero los pesos necesitan mantenerse. Least connections favorece destinos con menos conexiones; una conexión ociosa y otra con trabajo caro cuentan igual si la señal no distingue su coste.
Las variantes que consideran peticiones pendientes o latencia intentan reflejar mejor la carga. Deben comprobarse con distribuciones sesgadas y respuestas lentas: una señal retrasada puede enviar demasiado trabajo a un destino aparentemente libre. Ningún algoritmo crea capacidad cuando todos están saturados.
La afinidad mantiene ciertas solicitudes en el mismo destino mediante cookies o hashes. Puede facilitar una migración o aprovechar caché local, pero concentra usuarios activos y hace que la caída de una instancia afecte a sus sesiones. Si el estado necesario reside únicamente en memoria local, el rebalanceo puede perderlo. Un estado compartido con contrato de recuperación reduce esa dependencia; también añade una dependencia compartida.
Health checks y pool elegible
Los checks deben distinguir proceso vivo de instancia lista para recibir tráfico. Una respuesta HTTP rápida en una ruta de salud no demuestra que la ruta principal conserve su latencia bajo carga. Los umbrales, intervalos y timeouts definen cuánto tarda en detectarse una caída y cuándo vuelve una instancia al pool.
--- config: look: handDrawn --- flowchart TD client["Cliente"] --> admission["Admisión y plazo de la petición"] admission --> balance["Balanceador<br/>Flujo L4 o petición L7"] checks["Comprobaciones de salud"] -->|"Actualizar conjunto elegible"| balance balance -->|"Tráfico nuevo"| healthy["Instancias saludables"] balance -.->|"Detener tráfico nuevo"| draining["Instancia en vaciado"] draining -->|"Terminar trabajo en curso con plazo"| retire["Retirar instancia"] healthy --> data[("Estado autoritativo compartido")] draining --> data
Un check que consulte cada dependencia en cada instancia puede amplificar una caída compartida. Si todas se declaran enfermas porque la base está lenta, retirarlas no arregla la base. Conviene definir qué fallos justifican retirar una instancia y cuáles requieren degradación o limitar admisión. La política cuando todos los destinos fallan el check debe revisarse en el producto: puede rechazar o intentar seguir enviando tráfico, con consecuencias distintas. ALB puede hacer fail-open cuando todos los targets de un grupo están unhealthy en todas las AZ habilitadas, enviando tráfico a ellos. El diagrama muestra el camino habitual con destinos saludables, no una garantía de que todo tráfico evite destinos enfermos.
Una instancia nueva necesita capacidad lista y, cuando aplica, calentamiento de conexiones o cachés antes de recibir su proporción completa. Publicarla como saludable mientras inicia trabajo costoso desplaza el problema a las primeras peticiones.
Sobrecarga y reintentos
El balanceo debe acompañarse de colas acotadas, deadlines y límites de admisión. Dejar crecer una cola indefinidamente aumenta la espera y consume recursos incluso después de que el cliente haya abandonado. La señal relevante incluye trabajo activo y pendiente por destino, además de peticiones por segundo.
Un timeout puede ocurrir después de que el destino haya confirmado una escritura. Reenviar a otra instancia no revela si la primera terminó. Las operaciones reintentables necesitan idempotencia y un identificador de la misma operación, estable entre reintentos según el contrato. El balanceador no vuelve atómica una operación distribuida ni garantiza ejecución exactamente una vez.
Los reintentos deben tener presupuesto, backoff y jitter. Si cliente, gateway y aplicación reintentan varias veces, multiplican la carga durante el incidente. También deben conservar el deadline total: iniciar otra petición sin tiempo suficiente consume capacidad con poca posibilidad de entregar un resultado útil.
Failover y draining
La disponibilidad requiere redundancia del balanceador y destinos en dominios de fallo apropiados. Debe comprobarse qué ocurre al perder una zona: el tráfico restante puede superar la capacidad de los supervivientes. DNS, cachés de clientes y conexiones existentes pueden retrasar un cambio; no existe un failover instantáneo para todas las rutas por añadir una segunda dirección.
Draining retira un destino de la selección de tráfico nuevo y permite terminar trabajo en curso durante una ventana definida. El despliegue debe coordinar el deregistro, la señal de terminación del proceso y el plazo de cierre. Al vencer ese plazo, una conexión larga puede cortarse; una escritura puede dejar un resultado incierto para el cliente. Una cola consumida por la instancia requiere además acknowledgements e idempotencia propios.
Qué verificar
La prueba útil combina carga desigual, un destino lento, caída de una zona y un despliegue con conexiones persistentes. Se debe hacer coincidir un timeout con una escritura confirmada y comprobar el reintento. Durante draining, hay que observar tráfico nuevo, trabajo pendiente y cierre al vencer el plazo.
Las señales son p95/p99 de la petición completa, errores y rechazos, distribución de trabajo por instancia, colas, conexiones activas y tiempo de detección y recuperación. La conclusión debe indicar la capacidad que queda tras el fallo y qué operaciones pueden quedar inciertas. Repartir destinos mejora la operación cuando estos límites también están definidos.