Pular para o conteúdo

Balanceamento de carga

Intermediário16 min de leituraredes
Antes disto:

O balanceador é o ponto onde disponibilidade deixa de ser teoria: é ele que tira do ar a réplica doente, que segura o tráfego durante o deploy e que decide o que acontece quando tudo está lento. Configurado sem atenção, ele transforma um problema pequeno em indisponibilidade — mandando tráfego para instâncias que ainda não subiram, ou cortando conexões no meio.

  • Camada 4 (TCP/UDP): encaminha pacotes sem olhar o conteúdo. Muito rápido, funciona com qualquer protocolo, e não sabe nada sobre requisição — não roteia por caminho, não reescreve cabeçalho, não faz retry.
  • Camada 7 (HTTP): entende requisição. Roteia por host e caminho, reescreve, faz retry, divide tráfego por peso, encerra TLS, aplica limite de taxa.

Use camada 7 para HTTP (é o caso comum). Use camada 4 para protocolo próprio, gRPC quando o balanceador L7 não o suporta bem, latência extrema ou quando o TLS precisa chegar intacto na aplicação.

Um detalhe que morde: com camada 4 e HTTP/2 ou gRPC, uma conexão longa carrega muitas requisições — o balanceamento acontece por conexão, e uma réplica pode ficar sobrecarregada enquanto outra ociosa. É o motivo mais comum para escolher L7 (ou um mesh) em serviços gRPC.

  • Round robin: distribuição igual. Bom quando as requisições têm custo parecido.
  • Least connections: manda para quem tem menos conexão ativa. Melhor quando o tempo de resposta varia muito.
  • Least outstanding requests / EWMA: leva em conta latência recente; é o mais robusto contra a réplica lenta que continua aceitando tráfego.
  • Hash (IP, cabeçalho, cookie): mesma origem sempre no mesmo destino. Útil para cache local; ruim para distribuição uniforme.

Sessão fixa (sticky session) resolve o sintoma de estado guardado no processo, e cria outros: distribuição desigual, deploy que derruba sessão, escala que não alivia quem já está preso. Prefira sessão em Redis ou em token assinado, e mantenha a aplicação sem estado.

O check ativo é o balanceador perguntando “você está bem?”. O passivo é ele observando as respostas reais e tirando do rodízio quem falha. Use os dois.

O erro que mais causa incidente é usar um endpoint para tudo. Separe:

  • Liveness: o processo está vivo? Falhou, reinicia.
  • Readiness: pode receber tráfego agora? É este que o balanceador consulta — e ele deve considerar dependências essenciais (banco, cache), sem exagerar.
  • Startup: ainda está iniciando? Evita matar aplicação com boot lento.
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
periodSeconds: 5
failureThreshold: 3 # 15s até sair do rodízio
livenessProbe:
httpGet: { path: /livez, port: 8080 }
periodSeconds: 10
failureThreshold: 6 # tolerante: matar cedo demais piora incidente

Cuidado com o readiness que checa um banco compartilhado: se o banco oscilar, todas as réplicas ficam not ready ao mesmo tempo e o serviço some — quando parte dele ainda funcionaria. Verifique a dependência com tolerância, ou degrade em vez de sair do ar.

Ao remover uma réplica, o balanceador precisa parar de mandar tráfego novo e esperar o que está em andamento. Sem isso, cada deploy gera erros 502 e o time aprende a “reimplantar fora do pico” em vez de corrigir a causa.

A sequência correta, em Kubernetes:

lifecycle:
preStop:
exec: { command: ["sleep", "10"] } # tempo para sair do endpoint antes de terminar
terminationGracePeriodSeconds: 45 # maior que a requisição mais longa + preStop

O detalhe não óbvio: a remoção do endpoint e o SIGTERM acontecem em paralelo. O sleep no preStop dá tempo de o balanceador perceber a saída antes de a aplicação começar a fechar. Do lado da aplicação: trate SIGTERM encerrando o servidor com graça, sem aceitar conexão nova.

Configure também o tempo de drenagem no próprio balanceador (deregistration_delay no ALB, por exemplo) coerente com esses valores.

Timeout mal calibrado transforma lentidão em queda total. A regra: o timeout de fora precisa ser maior que a soma do que acontece dentro, e cada camada precisa desistir antes que quem a chamou desista.

cliente 30s
CDN 25s
balanceador 20s (idle timeout maior que o keep-alive da aplicação!)
aplicação 15s
chamada ao banco 5s, com no máximo 1 retry

Dois cuidados que evitam erro difícil de diagnosticar: o idle timeout do balanceador deve ser menor que o keep-alive do servidor de aplicação (senão o balanceador reutiliza uma conexão que o backend acabou de fechar, e você vê 502 esporádicos); e retry só em operação idempotente, com limite baixo — retry em cascata multiplica a carga no pior momento.

Por fim, meça pelo balanceador: a métrica dele (5xx por alvo, latência, alvos saudáveis) é o sintoma mais próximo do usuário e costuma ser o melhor sinal de alerta que você tem.

Próximo passo: tire carga da origem e aproxime o conteúdo em CDN e cache.