Um único servidor tem um teto. Não é um teto de configuração, é físico: um número finito de núcleos, de memória e de conexões TCP abertas ao mesmo tempo. Quando o tráfego passa desse ponto, a resposta óbvia é colocar mais máquinas. E aí aparece um problema que não existia com uma máquina só: o cliente digita um endereço, e esse endereço precisa apontar para alguma coisa. Para qual das máquinas?
O load balancer é a resposta a essa pergunta. Por fora ele parece simples, um distribuidor de requisições. Mas é na lógica que decide para onde cada requisição vai que mora quase tudo que importa.
O problema que ele resolve
Existem dois caminhos para aguentar mais carga. O vertical troca a máquina por uma maior: simples, mas tem teto físico e continua sendo um ponto único de falha. O horizontal coloca várias máquinas em paralelo: não tem teto teórico e tolera falhas, mas exige que alguém, na frente, decida quem atende cada requisição.
┌────────────────┐
cliente ──────────▶│ load balancer │
└────────┬───────┘
│ escolhe um alvo
┌─────────────┼─────────────┐
▼ ▼ ▼
servidor A servidor B servidor CEsse intermediário entrega três coisas ao mesmo tempo, e vale separar as três porque elas costumam ser tratadas como uma só:
- Distribuição: nenhum servidor afoga enquanto outro fica ocioso.
- Disponibilidade: um servidor morto sai de rotação sozinho, sem intervenção humana.
- Abstração: o cliente conhece um endereço estável, enquanto você adiciona, remove e troca máquinas atrás dele sem que ninguém perceba.
A terceira é a mais subestimada. É ela que torna deploy sem downtime, blue/green e canary possíveis, porque desacopla "o endereço que o mundo conhece" de "as máquinas que existem agora".
O caminho de uma requisição
O fluxo completo, do lado de fora para dentro:
- O cliente resolve
app.exemplo.comno DNS e recebe o IP do load balancer. - O cliente abre uma conexão TCP com o load balancer e envia a requisição.
- O load balancer consulta sua lista de alvos, filtrada apenas pelos que estão marcados como saudáveis naquele instante.
- Ele aplica um algoritmo de seleção para escolher um deles.
- Encaminha a requisição, recebe a resposta e devolve ao cliente.
- Registra métricas: latência, código de status, conexões abertas, bytes trafegados.
Os passos 3 e 4 são a lógica propriamente dita. O resto é encanamento. Vamos abrir os dois.
A lista de alvos e os health checks
O load balancer não confia cegamente na configuração dele. Ele sonda cada alvo continuamente, num intervalo fixo, tipicamente algo como:
GET /health HTTP/1.1
Host: 10.0.1.42
espera: HTTP 200 em até 5 segundos, a cada 30 segundosCada alvo tem uma máquina de estados própria:
| Estado | Como se entra | O que acontece |
|---|---|---|
healthy |
N sondagens bem-sucedidas seguidas | Recebe tráfego normalmente |
unhealthy |
N sondagens falhas seguidas | Sai de rotação imediatamente |
draining |
Remoção deliberada (deploy, scale in) | Para de receber conexões novas, termina as abertas |
O draining é a parte que passa despercebida e resolve um problema real: ao remover uma instância, cortar as conexões em andamento derruba requisições de usuários que já estavam sendo atendidos. O período de drenagem existe para que a instância termine o que começou antes de morrer.
Duas decisões de configuração que definem se o health check ajuda ou atrapalha:
Limiares consecutivos, nunca falha única. Uma sondagem falha pode ser um soluço de rede. Exigir duas ou três falhas seguidas evita o flapping, o servidor entrando e saindo de rotação a cada minuto. O outro extremo também custa caro: um limiar folgado demais mantém tráfego indo para um buraco negro por mais tempo do que deveria.
Profundidade certa no endpoint. Um /health que só responde 200 sem verificar nada mente: a instância pode estar de pé sem conseguir falar com o banco. Um /health que verifica todas as dependências mente ao contrário: se todas as instâncias compartilham o mesmo banco e o banco engasga, todos os checks falham juntos e o load balancer tira a frota inteira de rotação, transformando uma degradação em queda total. O equilíbrio prático é verificar o que é local à instância e tratar dependências compartilhadas com timeout curto e tolerância.
Os algoritmos de seleção
Aqui está o coração da lógica. Cada algoritmo assume uma coisa diferente sobre o seu tráfego, e o algoritmo certo é o que assume o que é verdade no seu caso.
Round robin. Alterna em ordem: A, B, C, A, B, C. Não guarda estado nenhum além do último índice usado. É justo só quando duas condições valem ao mesmo tempo: todas as requisições custam mais ou menos o mesmo, e todos os servidores têm a mesma capacidade. Na prática raramente as duas valem.
Weighted round robin. Mesma ideia, com pesos. O servidor A tem o dobro de vCPU, então recebe peso 2 enquanto B e C ficam com 1. Também é o mecanismo por trás de deploy canário: peso 19 na versão antiga, peso 1 na nova, e você observa as métricas com 5% do tráfego real antes de comprometer o resto.
Least connections. Escolhe o alvo com menos conexões abertas no momento. Esse é adaptativo, e a diferença é grande: se o servidor B travou numa consulta lenta, a contagem de conexões dele fica alta e ele naturalmente para de receber trabalho novo, sem que ninguém precise detectar nada. Sempre que a duração das requisições varia, e ela quase sempre varia, esse algoritmo ganha do round robin.
Least outstanding requests. É o refinamento que o ALB da AWS usa. Conta requisições em voo, não conexões. A distinção importa em HTTP/2 e gRPC, onde uma única conexão carrega dezenas de requisições simultâneas e contar conexões deixa de dizer qualquer coisa sobre carga.
Least response time. Combina conexões ativas com a latência observada. Reage ao servidor que está tecnicamente vivo, passa no health check, mas está degradado e respondendo em 800ms em vez de 40ms.
Hash. Calcula um hash de algo estável na requisição e mapeia para um alvo:
alvo = hash(ip_origem, porta_origem, ip_destino, porta_destino) % qtd_alvosO mesmo fluxo cai sempre no mesmo servidor. É assim que o NLB funciona, e é isso que torna o balanceamento de camada 4 tão barato: não há estado por requisição para manter, é aritmética. A fraqueza clássica está no % qtd_alvos, porque mudar o número de alvos remapeia praticamente todos os fluxos de uma vez. Por isso sistemas sérios usam consistent hashing, onde alvos e chaves são posicionados num anel e adicionar um servidor move apenas 1/N das chaves em vez de todas.
Random with two choices. Sorteia dois alvos e manda para o menos carregado dos dois. Parece grosseiro, mas o resultado chega muito perto do least connections verdadeiro sem precisar manter uma visão global do estado da frota, o que importa quando o balanceamento é feito por muitos proxies distribuídos que não se falam.
Sticky sessions, e por que elas brigam com o objetivo
Se a sua aplicação guarda sessão na memória do servidor, a requisição 2 precisa chegar no mesmo servidor da requisição 1. O load balancer resolve isso injetando um cookie:
Set-Cookie: AWSALB=<alvo codificado>; Expires=Wed, 26 Aug 2026 12:00:00 GMTNa requisição seguinte ele lê o cookie e roteia direto, ignorando o algoritmo.
Funciona, mas repare no que aconteceu: você desligou justamente a lógica que fazia o load balancer valer a pena. O servidor que morre leva as sessões dos usuários grudados nele. A carga fica desigual porque usuários se acumulam onde caíram. Deploy vira evento disruptivo. Auto scaling perde eficácia, já que as instâncias novas só recebem usuários novos.
A saída quase sempre é a mesma: tornar a aplicação stateless e mover a sessão para Redis, DynamoDB ou um token assinado, deixando o load balancer livre para balancear de verdade. Sticky session é remédio para uma decisão de arquitetura da aplicação, não um recurso para se buscar de propósito.
Camada 4 e camada 7, mecanicamente
Essa é a divisão que produz as duas famílias de load balancer, e ela fica muito mais clara olhando o que acontece com os pacotes. Se as camadas ainda não estão frescas, elas estão em Modelo OSI e TCP/IP.
Camada 4 opera sobre TCP e UDP. Ele enxerga endereços IP e portas, e nada além disso. Decide uma única vez, quando a conexão é aberta, e todo pacote daquele fluxo segue o mesmo caminho. O payload é um bloco opaco de bytes que ele nunca precisa decifrar, então TLS passa direto sem ser terminado. A implementação chega a ser pouco mais do que reescrever cabeçalhos e encaminhar, e é por isso que ele alcança milhões de conexões por segundo com latência na casa dos microssegundos.
Camada 7 termina a conexão. Ele completa o handshake TCP com o cliente, decifra o TLS, faz o parse da requisição HTTP e só então abre uma conexão própria com o backend. São duas conexões independentes, e o load balancer é um proxy completo no meio.
É essa terminação que destrava tudo:
se path começa com /api/ → target group: api-servers
se host = admin.exemplo.com → target group: admin-servers
se header X-Beta = true → target group: canary
caso contrário → target group: web-serversComo ele reconstrói a requisição, também pode reescrever cabeçalhos, injetar o X-Forwarded-For com o IP real do cliente (que se perdeu no momento em que o load balancer passou a ser a origem da conexão com o backend), exigir autenticação antes de encaminhar, responder um redirect sem tocar em servidor nenhum, e reaproveitar conexões com o backend entre clientes diferentes.
O preço é CPU e latência. Decifrar e interpretar cada requisição é trabalho real, medido em milissegundos em vez de microssegundos.
| Camada 4 | Camada 7 | |
|---|---|---|
| O que enxerga | IP e porta | Requisição inteira |
| Quando decide | Uma vez, na abertura da conexão | A cada requisição |
| TLS | Pode passar sem decifrar | Termina e decifra |
| IP do cliente | Preservado | Vai para o X-Forwarded-For |
| Latência | Microssegundos | Milissegundos |
| Na AWS | NLB | ALB |
O DNS também é load balancing
Vale fechar com uma camada que fica acima de tudo isso. Devolver IPs diferentes para o mesmo nome já distribui tráfego antes mesmo de existir uma conexão. É o que o Route 53 faz com roteamento por latência, por geolocalização ou por pesos, e é assim que se balanceia entre regiões, já que um load balancer vive dentro de uma região só.
A fraqueza é o cache: o cliente segura o registro DNS pelo TTL e às vezes bem além dele, então failover por DNS não é rápido e não é confiável no detalhe. Daí a divisão de trabalho usual: DNS resolve a distribuição global e grosseira, o load balancer resolve a decisão fina dentro de cada região.
O modelo mental
Um load balancer é um ponto de decisão com estado, que mantém uma visão viva da saúde da sua frota e aplica uma política a cada requisição que chega. A camada em que ele opera define quanto da requisição ele consegue enxergar, o que ele enxerga define quão inteligente a política pode ser, e essa inteligência custa latência.
Todo o resto, os algoritmos, os health checks, a stickiness, é detalhe pendurado nesse único trade-off.
Como isso se conecta com o resto
O load balancer é uma peça de um laço maior. Ele extrai o máximo dos servidores que existem, mas não cria o próximo servidor quando os atuais saturam, e isso é trabalho do auto scaling, como vimos em Escalando para alta demanda. Os dois nem se falam diretamente, se comunicam por métricas num sistema central.
Do lado da rede, o endereço estável que ele expõe é o mesmo tipo de problema resolvido em IP privado, público e Elastic IP, e o motivo pelo qual failover por ENI só funciona dentro de uma AZ, discutido em ENI, é exatamente o motivo pelo qual o load balancer existe: ele é a estratégia de failover que atravessa zonas.
Referências
- AWS. Elastic Load Balancing. Visão geral dos tipos de load balancer e do modelo de target groups e health checks.
- NGINX. HTTP Load Balancing. Documentação dos algoritmos de seleção e de configuração de health checks ativos e passivos.
- Cloudflare. What is load balancing?. Introdução conceitual com foco em distribuição global e DNS.
- MITZENMACHER, Michael. The Power of Two Choices in Randomized Load Balancing. O artigo que mostra por que sortear dois alvos chega tão perto do ótimo.
- KARGER, David et al. Consistent Hashing and Random Trees. Origem do consistent hashing, a solução para o remapeamento em algoritmos baseados em hash.