Load Balancing: Distribuindo Tráfego para Alta Disponibilidade
Guia abrangente sobre estratégias de load balancing, algoritmos e implementação. Aprenda como distribuir tráfego através de múltiplos servidores efetivamente.
Este artigo também está disponível em inglês
O Que é Load Balancing?
Load balancing distribui tráfego de rede recebido através de múltiplos servidores para garantir:
- Nenhum servidor único fica sobrecarregado
- Alta disponibilidade (se um servidor falha, outros assumem)
- Melhor utilização de recursos
- Tempos de resposta melhorados
Pense nisso como filas de checkout em um supermercado - ao invés de uma fila longa, você tem múltiplos caixas atendendo clientes em paralelo.
Algoritmos de Load Balancing
1. Round Robin
Distribui requisições sequencialmente através de servidores.
Requisição 1 → Servidor A
Requisição 2 → Servidor B
Requisição 3 → Servidor C
Requisição 4 → Servidor A (volta ao início)
Prós:
- Simples de implementar
- Distribuição justa
- Funciona bem quando servidores têm capacidade similar
Contras:
- Não considera carga do servidor
- Não leva em conta diferenças de capacidade do servidor
class RoundRobinBalancer(private val servers: List<String>) {
private var current = 0
fun getNextServer(): String {
val server = servers[current]
current = (current + 1) % servers.size
return server
}
}
2. Weighted Round Robin
Como round robin, mas servidores com pesos maiores recebem mais requisições.
data class WeightedServer(val host: String, val weight: Int)
class WeightedRoundRobinBalancer(servers: List<WeightedServer>) {
private val expandedServers = mutableListOf<String>()
private var current = 0
init {
// Expandir baseado em pesos
servers.forEach { server ->
repeat(server.weight) {
expandedServers.add(server.host)
}
}
}
fun getNextServer(): String {
val server = expandedServers[current]
current = (current + 1) % expandedServers.size
return server
}
}
// Uso
val balancer = WeightedRoundRobinBalancer(
listOf(
WeightedServer("servidor-potente", 5),
WeightedServer("servidor-medio", 3),
WeightedServer("servidor-pequeno", 1)
)
)
// servidor-potente recebe 5x mais requisições que servidor-pequeno
3. Least Connections
Roteia para o servidor com menos conexões ativas.
data class ServerConnection(
val host: String,
var connections: Int = 0
)
class LeastConnectionsBalancer(servers: List<String>) {
private val servers = servers.map { ServerConnection(it) }
fun getNextServer(): String {
// Encontrar servidor com mínimo de conexões
val server = servers.minByOrNull { it.connections }
?: throw IllegalStateException("No servers available")
server.connections++
return server.host
}
fun releaseConnection(host: String) {
servers.find { it.host == host }?.let {
it.connections--
}
}
}
Melhor para: Conexões de longa duração (WebSockets, conexões de banco de dados)
4. Least Response Time
Roteia para o servidor com tempo de resposta mais rápido.
data class ServerMetrics(
val host: String,
var avgResponseTime: Double = 0.0,
var requestCount: Int = 0
)
class LeastResponseTimeBalancer(servers: List<String>) {
private val servers = servers.map { ServerMetrics(it) }
fun getNextServer(): String {
// Encontrar servidor com menor tempo médio de resposta
val server = servers.minByOrNull { it.avgResponseTime }
?: throw IllegalStateException("No servers available")
return server.host
}
fun recordResponse(host: String, responseTime: Double) {
servers.find { it.host == host }?.let { server ->
server.avgResponseTime =
(server.avgResponseTime * server.requestCount + responseTime) /
(server.requestCount + 1)
server.requestCount++
}
}
}
5. IP Hash / Consistent Hashing
Roteia baseado no IP do cliente, garantindo que o mesmo cliente sempre vai ao mesmo servidor.
class IPHashBalancer(private val servers: List<String>) {
fun getServerForIP(clientIP: String): String {
val hash = hashIP(clientIP)
val index = hash % servers.size
return servers[index]
}
private fun hashIP(ip: String): Int {
var hash = 0
ip.forEach { char ->
hash = ((hash shl 5) - hash) + char.code
hash = hash and hash // Converter para inteiro 32bit
}
return kotlin.math.abs(hash)
}
}
Melhor para: Afinidade de sessão (sticky sessions)
6. Random
Seleciona servidor aleatoriamente. Simples mas surpreendentemente efetivo.
class RandomBalancer(private val servers: List<String>) {
fun getNextServer(): String {
val index = (Math.random() * servers.size).toInt()
return servers[index]
}
}
Tipos de Load Balancers
Layer 4 (Camada de Transporte) Load Balancing
Roteia baseado em endereço IP e porta TCP/UDP.
Características:
- Rápido (apenas olha camada de rede)
- Não pode inspecionar headers HTTP
- Protocol agnostic
# Nginx TCP load balancing
stream {
upstream backend {
server backend1.example.com:3000;
server backend2.example.com:3000;
server backend3.example.com:3000;
}
server {
listen 80;
proxy_pass backend;
}
}
Layer 7 (Camada de Aplicação) Load Balancing
Roteia baseado em headers HTTP, cookies, caminho da URL, etc.
Características:
- Roteamento mais inteligente
- Pode rotear baseado em conteúdo
- Ligeiramente mais lento (precisa parsear HTTP)
# Nginx HTTP load balancing
http {
upstream api_servers {
server api1.example.com:3000;
server api2.example.com:3000;
}
upstream static_servers {
server static1.example.com:80;
server static2.example.com:80;
}
server {
listen 80;
# Rotear requisições de API para servidores de API
location /api {
proxy_pass http://api_servers;
}
# Rotear conteúdo estático para servidores estáticos
location /static {
proxy_pass http://static_servers;
}
}
}
Health Checks
Essencial para alta disponibilidade - apenas rotear para servidores saudáveis.
data class HealthyServer(
val host: String,
var healthy: Boolean = true,
var failedChecks: Int = 0
)
@Component
class LoadBalancerWithHealthCheck(
servers: List<String>,
private val restTemplate: RestTemplate
) {
private val servers = servers.map { HealthyServer(it) }
init {
// Verificar saúde a cada 10 segundos
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
{ checkHealth() },
0,
10,
TimeUnit.SECONDS
)
}
private fun checkHealth() {
servers.forEach { server ->
try {
val response = restTemplate.getForEntity(
"http://${server.host}/health",
String::class.java
)
if (response.statusCode.is2xxSuccessful) {
server.healthy = true
server.failedChecks = 0
} else {
handleFailedCheck(server)
}
} catch (e: Exception) {
handleFailedCheck(server)
}
}
}
private fun handleFailedCheck(server: HealthyServer) {
server.failedChecks++
// Marcar como não saudável após 3 falhas consecutivas
if (server.failedChecks >= 3) {
server.healthy = false
println("Servidor ${server.host} marcado como não saudável")
}
}
fun getNextServer(): String {
val healthyServers = servers.filter { it.healthy }
if (healthyServers.isEmpty()) {
throw IllegalStateException("Nenhum servidor saudável disponível")
}
// Usar round robin em servidores saudáveis
return healthyServers.first().host
}
}
Persistência de Sessão (Sticky Sessions)
Garantir que requisições do usuário vão ao mesmo servidor.
Baseado em Cookie
upstream backend {
ip_hash; # Abordagem simples
server backend1.example.com;
server backend2.example.com;
}
# Ou usar stickiness baseado em cookie
upstream backend {
server backend1.example.com;
server backend2.example.com;
sticky cookie srv_id expires=1h;
}
Nível de Aplicação
Armazenar sessões em armazenamento compartilhado:
// Ao invés de memória do servidor
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'seu-secret',
resave: false,
saveUninitialized: false
}));
Soluções de Load Balancer
Software Load Balancers
Nginx
upstream backend {
least_conn; # Algoritmo
server backend1.example.com:3000 weight=5;
server backend2.example.com:3000 weight=3;
server backend3.example.com:3000 backup;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
HAProxy
frontend http_front
bind *:80
default_backend servers
backend servers
balance leastconn
option httpchk GET /health
server server1 192.168.1.10:3000 check
server server2 192.168.1.11:3000 check
server server3 192.168.1.12:3000 check
Cloud Load Balancers
-
AWS ELB (Elastic Load Balancer)
- ALB (Application Load Balancer) - Layer 7
- NLB (Network Load Balancer) - Layer 4
-
Google Cloud Load Balancing
-
Azure Load Balancer
Boas Práticas
1. Use Health Checks
Sempre monitore saúde do servidor e remova servidores não saudáveis da rotação.
2. Habilite SSL Termination no Load Balancer
server {
listen 443 ssl;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
# Encaminhar para backend como HTTP
proxy_pass http://backend;
}
}
3. Implemente Connection Draining
Finalize graciosamente requisições existentes antes de remover um servidor.
async function drainServer(server) {
// Parar de enviar novas requisições
server.accepting = false;
// Aguardar conexões existentes finalizarem
while (server.activeConnections > 0) {
await sleep(1000);
}
// Agora é seguro remover
removeServer(server);
}
4. Monitore Métricas Chave
- Taxa de requisições por servidor
- Tempos de resposta
- Taxas de erro
- Conexões ativas
- Status de saúde do servidor
5. Planeje para Failover
Tenha servidores de backup prontos para lidar com tráfego se servidores primários falharem.
Padrões Comuns
Load Balancing Geográfico
Rotear usuários para datacenter mais próximo:
Usuários US East → servidores us-east-1
Usuários EU → servidores eu-west-1
Usuários Asia → servidores ap-southeast-1
Load Balancing de Microserviços
Cada serviço tem seu próprio load balancer:
API Gateway
↓
┌─────────┬─────────┬─────────┐
User LB Order LB Product LB
↓ ↓ ↓
User Svc Order Svc Product Svc
Auto-Scaling
Adicionar/remover servidores automaticamente baseado em carga:
if (averageCPU > 80) {
scaleUp();
}
if (averageCPU < 20 && serverCount > minServers) {
scaleDown();
}
Conclusão
Load balancing é essencial para construir sistemas escaláveis e altamente disponíveis. Principais lições:
- Escolha o algoritmo certo para seu caso de uso
- Sempre implemente health checks
- Monitore métricas do seu load balancer
- Planeje para falhas
- Comece simples e adicione complexidade conforme necessário
Mais importante: teste sua configuração de load balancing sob condições realistas de tráfego antes da produção!
Feliz balanceamento! ⚖️