È uscito il Corso Java Completo
Torna al blog

Cos'è il load balancing e come funziona

Come funziona il load balancing: algoritmi di distribuzione, health check, sessioni sticky, layer 4 e layer 7, e quando invece non ti serve affatto.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

10 min di lettura

Il sito cresce, il server inizia ad arrancare nelle ore di punta, e la parola che senti ripetere è "bilanciamento di carico". Sembra la risposta ovvia: metti due server invece di uno. Solo che, quando ci provi, saltano fuori le sessioni che si perdono, i server morti che continuano a ricevere traffico e una complessità che non avevi preventivato. In questo articolo trovi come funziona davvero, quali algoritmi esistono e cosa cambia tra loro, i problemi concreti che incontri — e perché in molti casi la risposta giusta è non farlo.

Cos'è

Il load balancing è la distribuzione delle richieste in arrivo su più server, in modo che nessuno di loro riceva più traffico di quanto riesca a gestire.

Chi visita il sito vede un solo indirizzo. Dietro, un componente chiamato bilanciatore riceve ogni richiesta e decide a quale server passarla.

Il secondo beneficio, spesso più importante del primo, è la disponibilità. Con un server solo, se quel server cade il sito è offline. Con tre server dietro un bilanciatore, se uno cade gli altri due continuano a rispondere e la maggior parte degli utenti non se ne accorge.

Perché serve (e le due ragioni sono diverse)

Vale la pena separarle, perché portano a scelte diverse.

Capacità. Un server ha un limite. Quando lo raggiungi, o compri una macchina più grande (scalabilità verticale) o ne aggiungi altre uguali (orizzontale). Il bilanciatore è ciò che rende possibile la seconda strada.

Tolleranza ai guasti. Anche se un server ti basterebbe in termini di potenza, due server ti permettono di riavviare, aggiornare o perdere una macchina senza andare offline. È il motivo per cui alcuni mettono un bilanciatore davanti a due server scarichi: non serve la capacità, serve non svegliarsi con il sito giù.

Se ti serve solo la seconda, esistono soluzioni più semplici — un secondo server in standby, o un provider con failover automatico — che costano meno complessità.

Gli algoritmi di distribuzione

Il bilanciatore deve decidere, richiesta per richiesta, dove mandarla. I criteri principali sono tre.

Round robin

Il più semplice: a turno. Prima richiesta al server 1, seconda al 2, terza al 3, quarta di nuovo al 1.

upstream backend {
    server 10.0.0.11:3000;
    server 10.0.0.12:3000;
    server 10.0.0.13:3000;
}

Funziona bene se i server sono identici e le richieste costano più o meno uguale. Il limite è che conta le richieste, non il lavoro. Se una richiesta su venti è un'esportazione CSV che occupa il server per dieci secondi, il round robin non lo sa e continua a mandargli traffico.

Esiste la variante pesata, utile quando le macchine non sono uguali:

upstream backend {
    server 10.0.0.11:3000 weight=3;   # macchina più potente
    server 10.0.0.12:3000 weight=1;
}

Least connections

Manda la richiesta al server che in quel momento ha meno connessioni attive.

upstream backend {
    least_conn;
    server 10.0.0.11:3000;
    server 10.0.0.12:3000;
}

È quasi sempre la scelta migliore quando le richieste hanno durate molto diverse. Il server occupato dall'esportazione CSV ha una connessione aperta in più, e riceve automaticamente meno traffico nuovo. Costa un pelo in più al bilanciatore, che deve tenere il conto, ma è irrilevante.

IP hash

Calcola una funzione hash sull'indirizzo IP di chi chiama e usa il risultato per scegliere il server. Lo stesso IP finisce sempre sullo stesso server.

upstream backend {
    ip_hash;
    server 10.0.0.11:3000;
    server 10.0.0.12:3000;
}

Serve a risolvere il problema delle sessioni, di cui tra poco. Ha due difetti seri: distribuisce male quando molti utenti condividono lo stesso IP (uffici, reti mobili, CDN o proxy davanti), e se togli o aggiungi un server la mappatura cambia per tutti, non solo per quelli del server rimosso.

AlgoritmoQuando usarloAttenzione
Round robinServer uguali, richieste similiIgnora quanto è occupato un server
Round robin pesatoMacchine di potenza diversaI pesi vanno rivisti a mano
Least connectionsRichieste di durata variabilePraticamente sempre una buona scelta
IP hashServe continuità di sessioneDistribuisce male dietro NAT o proxy

Gli health check

Un bilanciatore senza controlli di salute è peggio di nessun bilanciatore. Se il server 2 muore e il bilanciatore non se ne accorge, un terzo dei tuoi utenti riceve errori — e con il round robin li riceve a rotazione, quindi il sito sembra "rotto a intermittenza", che è il tipo di guasto più difficile da diagnosticare.

Ci sono due modi di controllare:

  • Passivo: il bilanciatore osserva le risposte reali. Se un server sbaglia N richieste di fila, viene messo da parte per un po'. È quello che fa Nginx nella versione libera.
  • Attivo: il bilanciatore chiama periodicamente un endpoint dedicato, indipendentemente dal traffico. Lo fanno HAProxy, Traefik, i bilanciatori dei provider cloud e Nginx Plus.
upstream backend {
    least_conn;
    server 10.0.0.11:3000 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:3000 max_fails=3 fail_timeout=30s;
}

Fai in modo che l'endpoint di controllo sia significativo. Un /health che restituisce sempre 200 OK senza verificare niente dichiara "sto bene" anche quando l'applicazione ha perso la connessione al database. Ma non esagerare nell'altro senso: se /health interroga il database e il database rallenta, il bilanciatore toglie tutti i server insieme e il sito va giù del tutto invece che a metà.

// Un compromesso ragionevole
app.get('/health', (req, res) => {
  res.status(200).send('ok');            // il processo risponde
});

app.get('/health/deep', async (req, res) => {
  // usato dal monitoraggio, non dal bilanciatore
  await db.query('SELECT 1');
  res.status(200).send('ok');
});

Il problema delle sessioni sticky

È il punto dove quasi tutti si scontrano con la realtà.

Un utente fa il login sul server 1. Il server 1 tiene la sessione in memoria. La richiesta successiva, per via del round robin, finisce sul server 2 — che non sa niente di quell'utente e lo rimanda alla pagina di login. Risultato: gli utenti vengono buttati fuori a caso.

La soluzione immediata è la sessione sticky: legare ogni utente a un server, con IP hash o con un cookie posto dal bilanciatore.

upstream backend {
    ip_hash;
    server 10.0.0.11:3000;
    server 10.0.0.12:3000;
}

Funziona, ed è un cerotto. I problemi che ti porti dietro sono tre: se il server a cui un utente è legato cade, quell'utente perde comunque la sessione; il carico si distribuisce peggio; e non puoi riavviare un server per aggiornarlo senza scollegare i suoi utenti.

La soluzione vera è togliere lo stato dai server applicativi. Metti le sessioni in un archivio condiviso — Redis è la scelta standard — e a quel punto ogni server è intercambiabile:

// express-session con Redis: qualunque server può servire qualunque utente
app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
}));

Lo stesso vale per i file caricati dagli utenti: se finiscono sul disco locale del server 1, il server 2 non li vede. Vanno su uno storage condiviso o su un object storage.

Usa le sessioni sticky come misura temporanea mentre sistemi lo stato condiviso, non come architettura definitiva.

Layer 4 o layer 7

Due modi di lavorare, con capacità diverse.

Layer 4 (trasporto). Il bilanciatore guarda solo indirizzi IP e porte. Non apre il contenuto della connessione, la inoltra e basta. È velocissimo, consuma pochissimo, e funziona con qualunque protocollo — non solo HTTP, ma anche database, code, connessioni TCP generiche.

Layer 7 (applicativo). Il bilanciatore legge la richiesta HTTP: dominio, percorso, intestazioni, cookie. Può quindi decidere in base al contenuto:

location /api/ {
    proxy_pass http://api_backend;
}

location /immagini/ {
    proxy_pass http://static_backend;
}

Con il layer 7 puoi terminare HTTPS nel bilanciatore, instradare per percorso o dominio, riscrivere le intestazioni, fare caching e sessioni sticky basate su cookie. Costa qualche microsecondo in più per richiesta, che nella pratica non noti.

Layer 4Layer 7
Cosa vedeIP e porteLa richiesta HTTP completa
ProtocolliQualunque TCP/UDPHTTP/HTTPS
Instradamento per percorsoNo
Terminazione TLSNo (passa attraverso)
CostoMinimoLeggermente superiore
QuandoDatabase, TCP puro, traffico enormeQuasi sempre, per il web

Per un sito o un'API, layer 7. Il layer 4 serve quando bilanci qualcosa che non è HTTP, o quando i volumi sono tali da rendere significativo anche quel margine.

Quando NON ti serve

Questa è la sezione che le guide saltano, ed è quella che ti fa risparmiare più tempo.

Verticale prima di orizzontale. Raddoppiare la RAM e i core di un VPS è un'operazione di dieci minuti e un clic. Passare a due server significa gestire lo stato condiviso, sincronizzare le versioni deployate, moltiplicare i log, aggiungere un pezzo che può guastarsi. Una macchina moderna da 8 core regge molto più traffico di quanto la maggior parte dei siti veda mai. Sali di taglia finché puoi permettertelo: è quasi sempre più economico dell'ora di lavoro che ti costa la complessità.

Prima verifica se il problema è davvero la capacità. Nove volte su dieci il server non è saturo, è bloccato: query senza indici, chiamate esterne senza timeout, assenza di caching. Aggiungere un secondo server a un'applicazione con una query lenta ti dà due server lenti. Prima misura — il tema è in come velocizzare un sito web.

Se il problema sono i file statici e la latenza geografica, ti serve una CDN, non un bilanciatore. Costa meno, si attiva in un pomeriggio e toglie dal server la maggior parte delle richieste.

Se ti serve solo protezione dai picchi anomali, guarda a Cloudflare e alla gestione degli attacchi DDoS: un bilanciatore non ti difende da un attacco, distribuisce solo il danno.

Attenzione al bilanciatore come singolo punto di guasto. Hai messo tre server per non andare offline, e ora tutto passa da una macchina sola. I bilanciatori gestiti dei provider cloud sono già ridondati; se te lo fai in casa con un Nginx su un VPS, hai spostato il problema, non risolto.

In sintesi

Il load balancing distribuisce le richieste su più server, per aumentare la capacità e per non andare offline quando una macchina cade.

L'algoritmo predefinito ragionevole è least connections. Round robin va bene se i server sono uguali e le richieste simili; IP hash risolve le sessioni ma distribuisce male.

Gli health check non sono opzionali, e devono controllare qualcosa di vero senza però mettere tutti i server fuori uso appena il database rallenta.

Le sessioni sticky sono un cerotto: la soluzione è rendere i server senza stato, con sessioni e file su archivi condivisi.

Per il web, layer 7. Il layer 4 serve per protocolli diversi da HTTP.

E prima di tutto: prova a crescere in verticale. Un server più grande è quasi sempre la mossa giusta prima di passare a due.

Il pezzo che sta sotto a tutto questo è il reverse proxy, e il server con cui lo realizzi lo scegli in Nginx o Apache. Se stai andando verso più macchine gestite in modo automatico, il passo successivo è Kubernetes.