7 funções do Load Balancer: explicação detalhada com exemplos

Este artigo tem um vídeo curto:TikTok

1. Visão geral

Quando se fala em Load Balancer (LB), muita gente pensa apenas: “ele distribui requisições entre vários servidores”. Mas na verdade, LB faz muito mais — desde reduzir carga SSL, manter sessões de usuário, até proteger contra DDoS e monitorar a saúde do backend.

Este artigo aborda 7 funções principais do Load Balancer, cada uma com exemplos de configuração ou código específico (nginx, Spring Boot, Kubernetes) para você entender bem como funciona em um sistema real. Depois de ler, você saberá quando ativar cada recurso no LB e evitará alguns erros de configuração comuns.

2. Distribuição de tráfego (Traffic Distribution)

Esta é a função mais básica: o LB recebe requisições do cliente e as encaminha para um dos vários servidores backend seguindo algum 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 instâncias do OrderService. Por padrão, nginx usa round robin — requisição 1 vai para .11, requisição 2 vai para .12, requisição 3 vai para .13, depois volta para .11. Se uma instância processar requisições mais pesadas, você pode mudar para least_conn; para fazer o nginx priorizar o servidor com menos conexões abertas.

Observação: Round robin não se importa se o servidor está saudável ou não. Se as instâncias têm capacidades diferentes (por exemplo, uma máquina com configuração mais poderosa), use weight:

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

3. SSL Termination

Em vez de deixar cada backend descriptografar TLS sozinho (custoso em CPU), você move isso para o LB. O LB mantém o certificado, descriptografa HTTPS para HTTP puro e depois encaminha para a rede 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, não precisa de TLS
        proxy_set_header X-Forwarded-Proto https;
    }
}

Graças a isso, OrderService escrito em Spring Boot não precisa configurar nada em server.ssl.* — ele apenas roda HTTP normal na porta 8080. O header X-Forwarded-Proto ajuda o backend a saber que a requisição original era HTTPS para gerar URLs de redirecionamento corretamente.

Observação: Se a rede interna entre LB e backend não é confiável (por exemplo, passando por várias subnets, clouds diferentes), não apenas faça termination — faça “SSL bridging”: o LB descriptografa e criptografa novamente com um certificado interno antes de encaminhar.

4. Manutenção de sessão (Session Persistence)

Quando o backend armazena sessão na memória (sessão local), requisições do mesmo usuário sempre devem ir para a mesma instância, caso contrário o usuário será “desconectado” aleatoriamente.

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

ip_hash faz hash do IP do cliente para escolher sempre o mesmo servidor no upstream. Outra forma é sticky session baseada em cookie — o LB adiciona automaticamente um cookie marcando a instância, funcionando mesmo quando o cliente está atrás de um NAT compartilhado.

Uma solução mais robusta é remover sticky session e externalizar a sessão usando Spring Session + Redis:

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

Com essa configuração, a sessão é armazenada no Redis em vez da memória de cada instância. Qualquer servidor que receber uma requisição consegue ler a sessão — o LB pode usar round robin normal, sem precisar de ip_hash mais.

Observação: ip_hash pode causar desequilíbrio de carga se muitos usuários compartilham um NAT (por exemplo, um escritório de empresa) — todo o tráfego deles converge para 1 servidor. Priorize externalizar a sessão quando o sistema crescer.

5. Alta disponibilidade (High Availability)

O LB verifica continuamente o backend; se uma instância cair, o LB a remove automaticamente da lista de servidores disponíveis e desvia o tráfego para instâncias que ainda estão vivas — tudo isso acontece sem o cliente perceber.

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 após 3 falhas consecutivas de requisições, o nginx marca o servidor como “down” por 30 segundos (fail_timeout), não enviando mais requisições para ele. Após 30 segundos, nginx tenta novamente — se o servidor se recuperou, ele volta à rotação.

[14:02:01] 10.0.1.12 timeout -> falha 1
[14:02:05] 10.0.1.12 timeout -> falha 2
[14:02:09] 10.0.1.12 timeout -> falha 3 -> marcado DOWN por 30s
[14:02:09] tráfego redirecionado para 10.0.1.11
[14:02:39] 10.0.1.12 retestado -> saudável -> de volta ao pool

Graças a esse mecanismo, uma instância que cai não derruba todo o sistema — essa é exatamente a base da alta disponibilidade na camada de rede.

6. Escalabilidade horizontal (Scalability)

O LB é um pré-requisito para que o horizontal scaling funcione: você adiciona uma nova instância e o LB automaticamente a detecta e distribui carga para ela, sem precisar modificar o código do 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

Aqui, o Kubernetes Service (atuando como LB interno) distribui automaticamente o tráfego entre os pods order-service. O HorizontalPodAutoscaler monitora a CPU — quando ultrapassa 70%, Kubernetes cria pods adicionais (máximo 10), e o LB imediatamente adiciona os novos pods à lista de servidores sem precisar fazer redeploy ou restart.

Observação: O scaling só é eficaz se o backend for stateless (ou a sessão foi externalizada como na seção 4). Se não for, novos pods adicionados não receberão a sessão anterior do usuário.

7. Mitigação de DDoS (DDoS Mitigation)

Como o LB fica na borda, é o lugar ideal para bloquear tráfego anormal antes que ele atinja o backend — através de rate limiting ou distribuindo o tráfego de forma mais ampla.

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 no máximo 20 requisições/segundo. burst=10 permite ultrapassar brevemente com até 10 requisições enfileiradas, nodelay processa o burst imediatamente em vez de fazer throttling. Ultrapassando esse limite, nginx retorna 503 Service Temporarily Unavailable em vez de deixar as requisições se acumularem no backend.

Se você quiser rate limit na camada de aplicação (como complemento, não substituto do LB), use Bucket4j no 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("Too many requests");
    }
    return ResponseEntity.ok(orderService.findAll());
}

Observação: Rate limit por IP é fácil de contornar com spoofing de IP ou botnets distribuídos. Para ataques DDoS em larga escala, combine LB com CDN/WAF em uma camada anterior, LB é apenas a segunda linha de defesa.

8. Monitoramento da saúde do backend (Health Monitoring)

Diferente da seção 5 (reagir quando requisições falham), health monitoring é o LB ativamente chamar um endpoint periodicamente para saber se o servidor ainda está vivo — chamado de 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() {
        // verificar pool de conexões, retornar true/false
        return true;
    }
}

Com Spring Boot Actuator, você já tem um endpoint similar sem precisar escrever à mão:

<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

O LB (ou Kubernetes) chama /actuator/health a cada alguns segundos:

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

Escreva testes para garantir que o endpoint reaja corretamente quando o BD falha:

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

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void shouldReturn503WhenDatabaseDown() {
        // simular BD down via perfil de teste ou mock bean
        ResponseEntity<String> response =
            restTemplate.getForEntity("/health", String.class);

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

Este teste garante que o endpoint /health sempre retorne um status válido (200 ou 503) sem lançar exceções — porque se o health check em si falhar, o LB pode pensar que o servidor está down e remover incorretamente uma instância saudável.

Observação: O endpoint de health check deve ser leve e rápido (menos de alguns centenas de ms). Se ele consultar muitas tabelas ou chamar outros serviços, o próprio health check pode causar timeouts e ser removido injustamente pelo LB.

9. Conclusão

Load Balancer não é apenas uma “ferramenta de distribuição de carga” — ele também reduz carga SSL, mantém sessões, detecta falhas, suporta escalabilidade horizontal e é a primeira linha de defesa contra DDoS. Ao projetar seu sistema, você deve identificar claramente quais das 7 funções acima precisa para escolher o recurso certo ao configurar o LB — nginx, HAProxy, AWS ALB ou Kubernetes Service todos suportam a maioria desses casos de uso, apenas diferindo na forma de declaração.

Último conselho: não coloque todas as responsabilidades em um único LB. Combinar health checks ativos, rate limiting e externalização de sessão fará seu sistema escalar melhor e ser mais resiliente a falhas do que simplesmente ativar round robin e esquecer.

design-de-sistemasbalanceador-de-cargaescalabilidadealta-disponibilidadenginx