7 usos del Load Balancer: explicación detallada con ejemplos

Este artículo tiene un vídeo corto:TikTok

1. Descripción general

Cuando se menciona Load Balancer (LB), muchos solo piensan: “distribuye solicitudes a varios servidores”. De hecho, LB hace mucho más que eso: desde descargar SSL, mantener sesiones de usuario, hasta protegerse contra DDoS y monitorear la salud del backend.

Este artículo cubre 7 usos principales del Load Balancer, cada uno con ejemplos de configuración o código específico (nginx, Spring Boot, Kubernetes) para que visualices cómo funciona en un sistema real. Después de leerlo, sabrás cuándo habilitar cada característica en el LB y evitarás algunos errores de configuración comunes.

2. Distribución de tráfico (Traffic Distribution)

Esta es la función más básica: el LB recibe solicitudes del cliente y las envía a uno de varios servidores backend según algún algoritmo: round robin, least connections, weighted round robin…

upstream order_service {
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
    server 10.0.1.13:8080;
}

server {
    listen 80;
    location /orders/ {
        proxy_pass http://order_service;
    }
}

upstream declara 3 instancias de OrderService. De forma predeterminada, nginx usa round robin: la solicitud 1 va a .11, la solicitud 2 va a .12, la solicitud 3 va a .13, y luego vuelve a .11. Si una instancia procesa solicitudes más pesadas, puedes cambiar a least_conn; para que nginx priorice el servidor con menos conexiones abiertas.

Nota: Round robin no se preocupa por si el servidor está en buen o mal estado. Si tus instancias tienen capacidades diferentes (por ejemplo, una máquina con especificaciones más potentes), usa weight:

server 10.0.1.11:8080 weight=3;
server 10.0.1.12:8080 weight=1;

3. SSL Termination

En lugar de que cada backend descifre TLS por su cuenta (costoso en CPU), lo dejas en manos del LB. El LB mantiene el certificado, descifra HTTPS a HTTP puro y luego lo reenvía a la red interna.

server {
    listen 443 ssl;
    server_name api.nonladev.com;

    ssl_certificate     /etc/nginx/certs/nonladev.crt;
    ssl_certificate_key /etc/nginx/certs/nonladev.key;

    location / {
        proxy_pass http://order_service;   # HTTP puro, sin necesidad de TLS
        proxy_set_header X-Forwarded-Proto https;
    }
}

Gracias a esto, OrderService escrito en Spring Boot no necesita ninguna configuración server.ssl.*: solo ejecuta HTTP normal en el puerto 8080. El encabezado X-Forwarded-Proto ayuda al backend a saber que la solicitud original fue HTTPS para generar la URL de redirección correcta.

Nota: Si la red interna entre el LB y el backend no es confiable (por ejemplo, a través de múltiples subredes o nubes diferentes), no solo hagas termination: usa “SSL bridging”: el LB descifra y luego lo cifra de nuevo con un certificado interno antes de reenviarlo.

4. Persistencia de sesión (Session Persistence)

Cuando el backend almacena la sesión en memoria (sesión local), las solicitudes del mismo usuario siempre deben ir a la misma instancia; de lo contrario, el usuario será “desconectado” aleatoriamente.

upstream order_service {
    ip_hash;                       # mismo IP del cliente -> mismo servidor
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

ip_hash utiliza el hash de la IP del cliente para seleccionar de forma consistente un servidor en el upstream. Otra forma es sticky session basado en cookies: el LB adjunta automáticamente una cookie que marca la instancia, lo que funciona incluso cuando el cliente está detrás de un NAT con IP compartida.

Una solución más robusta es abandonar sticky session y externalizar la sesión usando Spring Session + Redis:

@Configuration
@EnableRedisHttpSession
public class SessionConfig {
}
spring.data.redis.host=redis.internal
spring.data.redis.port=6379

Con esta configuración, la sesión se almacena en Redis en lugar de en la memoria de cada instancia. La solicitud, sin importar a qué servidor vaya, puede leer la sesión: el LB puede usar round robin normal, sin necesidad de ip_hash.

Nota: ip_hash puede causar desequilibrio de carga si muchos usuarios comparten un NAT (por ejemplo, una oficina corporativa): todo su tráfico se concentra en 1 servidor. Prioriza externalizar la sesión cuando el sistema ha crecido.

5. Alta disponibilidad (High Availability)

El LB comprueba continuamente el backend; si una instancia falla, el LB la elimina automáticamente de la lista de servicio y redirige el tráfico a las instancias que siguen funcionando: todo sucede sin que el cliente se entere.

upstream order_service {
    server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
    server 10.0.1.12:8080 max_fails=3 fail_timeout=30s;
}

max_fails=3 significa que después de 3 solicitudes fallidas consecutivas, nginx marca ese servidor como “down” durante 30 segundos (fail_timeout) y no envía nuevas solicitudes. Después de 30 segundos, nginx lo intenta de nuevo: si el servidor se ha recuperado, se vuelve a incluir en la rotación.

[14:02:01] 10.0.1.12 timeout -> fail count 1
[14:02:05] 10.0.1.12 timeout -> fail count 2
[14:02:09] 10.0.1.12 timeout -> fail count 3 -> marked DOWN for 30s
[14:02:09] tráfico redirigido a 10.0.1.11
[14:02:39] 10.0.1.12 reintentado -> en buen estado -> de vuelta en el pool

Gracias a este mecanismo, que una instancia falle no hace colapsar todo el sistema: este es el fundamento de la alta disponibilidad en la capa de red.

6. Escalabilidad horizontal (Scalability)

El LB es un requisito previo para que el escalado horizontal funcione: agregas una nueva instancia, el LB la reconoce automáticamente y distribuye la carga, sin necesidad de modificar el código del backend.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 2
  template:
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.4.0
          ports:
            - containerPort: 8080
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Aquí, el Kubernetes Service (que actúa como LB interno) distribuye automáticamente el tráfico entre los pods order-service. El HorizontalPodAutoscaler monitorea la CPU: cuando excede el 70%, Kubernetes crea pods adicionales (máximo 10), y el LB inmediatamente agrega los nuevos pods a la lista de servicio sin necesidad de redeploy ni reinicio.

Nota: El escalado solo es efectivo si el backend es stateless (o si la sesión ya está externalizada como en la sección 4). De lo contrario, los nuevos pods no tendrán acceso a las sesiones antiguas del usuario.

7. Mitigación de ataques DDoS (DDoS Mitigation)

Como el LB está en el borde, es el lugar ideal para bloquear tráfico anómalo antes de que alcance el backend, usando rate limiting o dispersando el tráfico de forma más amplia.

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s;

server {
    location /orders/ {
        limit_req zone=api_limit burst=10 nodelay;
        proxy_pass http://order_service;
    }
}

limit_req_zone limita cada IP a un máximo de 20 solicitudes/segundo. burst=10 permite que se encolen un máximo de 10 solicitudes en exceso a corto plazo, nodelay procesa el pico inmediatamente en lugar de hacer que se acumule. Exceder este límite, nginx devuelve 503 Service Temporarily Unavailable en lugar de permitir que las solicitudes se acumulen en el backend.

Si deseas rate limiting en la capa de aplicación (además, no en lugar del LB), usa Bucket4j en Spring Boot:

<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>8.10.1</version>
</dependency>
Bucket bucket = Bucket.builder()
    .addLimit(Bandwidth.simple(20, Duration.ofSeconds(1)))
    .build();

@GetMapping("/orders")
public ResponseEntity<?> getOrders() {
    if (!bucket.tryConsume(1)) {
        return ResponseEntity.status(429).body("Demasiadas solicitudes");
    }
    return ResponseEntity.ok(orderService.findAll());
}

Nota: El rate limiting por IP puede eludirse con suplantación de dirección IP o botnets distribuidas. Para DDoS a gran escala, necesitas combinar el LB con CDN/WAF en la capa anterior; el LB es solo la segunda línea de defensa.

8. Monitoreo de la salud del backend (Health Monitoring)

A diferencia de la sección 5 (reaccionar cuando falla una solicitud), el monitoreo de salud es cuando el LB activamente llama a un endpoint periódicamente para saber si el servidor sigue vivo: esto se llama active health check.

@RestController
public class HealthController {

    @GetMapping("/health")
    public ResponseEntity<String> health() {
        boolean dbUp = checkDatabaseConnection();
        if (!dbUp) {
            return ResponseEntity.status(503).body("DB_DOWN");
        }
        return ResponseEntity.ok("OK");
    }

    private boolean checkDatabaseConnection() {
        // revisa el pool de conexiones, devuelve true/false
        return true;
    }
}

Con Spring Boot Actuator, tienes un endpoint similar sin necesidad de escribirlo manualmente:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoints.web.exposure.include=health
management.endpoint.health.show-details=always

El LB (o Kubernetes) llama a /actuator/health cada pocos segundos:

livenessProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  periodSeconds: 5
  failureThreshold: 3

Escribe una prueba para asegurar que el endpoint responde correctamente cuando la BD falla:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class HealthControllerTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void shouldReturn503WhenDatabaseDown() {
        // simula BD down a través de un perfil de prueba o mock bean
        ResponseEntity<String> response =
            restTemplate.getForEntity("/health", String.class);

        assertThat(response.getStatusCode().value()).isIn(200, 503);
    }
}

Esta prueba asegura que el endpoint /health siempre devuelve un estado válido (200 o 503) sin lanzar una excepción, porque si el health check falla en sí mismo, el LB puede interpretar erróneamente que el servidor está down y eliminar incorrectamente una instancia que está funcionando.

Nota: El endpoint de health check debe ser ligero y rápido (menos de algunos cientos de ms). Si consulta muchas tablas o llama a otros servicios, el propio health check puede causar timeouts y ser eliminado erróneamente por el LB.

9. Conclusión

El Load Balancer no es solo “un distribuidor de carga”: también descarga SSL, mantiene sesiones, detecta fallos, respalda escalado horizontal y es la primera línea de defensa contra DDoS. Al diseñar un sistema, debes identificar claramente cuál de los 7 usos anteriores necesitas para elegir las características correctas al configurar el LB: nginx, HAProxy, AWS ALB o Kubernetes Service todos respaldan la mayoría de estos casos de uso, solo difieren en cómo se declaran.

Consejo final: no concentres toda la responsabilidad en un único LB. Combinar health checks activos, rate limiting y sesiones externalizadas hará que tu sistema escale mejor y sea más tolerante a fallos que solo activar round robin y dejarlo así.

system-designload-balancerscalabilityhigh-availabilitynginx