Bilanciamento del carico
Aumentare le istanze portando online nuove macchine virtuali quando il traffico aumenta è una strategia efficace per ridimensionare le risorse al fine di soddisfare la domanda. La possibilità di eseguire rapidamente il provisioning di macchine virtuali è essenziale per ottenere l'elasticità. Portare online altri server non è utile se il traffico non viene distribuito tra di essi. Nel complesso, questo consente al sistema di gestire l'aumento del carico. È per questo che il bilanciamento del carico è fondamentale per l'elasticità, in quanto modifica dinamicamente il numero di risorse dedicate a un'attività.
La necessità di bilanciare il carico deriva da due requisiti di base. In primo luogo, l'elaborazione parallela migliora la velocità effettiva. Se un singolo server può gestire 5.000 richieste per unità di tempo, 10 server con carico perfettamente bilanciato possono gestire 50.000 richieste per unità di tempo. In secondo luogo, le risorse con carico bilanciato garantiscono una disponibilità superiore. Anziché inviare una richiesta a un server che fatica già a tenere il passo, un servizio di bilanciamento del carico può indirizzarla a un server con un carico più leggero. Anche se un server passa alla modalità offline e il servizio di bilanciamento del carico lo rileva, può indirizzare le richieste ad altri server.
Che cos'è il bilanciamento del carico?
Una forma ben nota di bilanciamento del carico è il DNS round robin, usato da molti servizi Web di grandi dimensioni per distribuire le richieste tra più server. Nello specifico, più server front-end, ognuno con un indirizzo IP univoco, condividono un nome DNS. Per bilanciare il numero di richieste in ogni server Web, le grandi società come Google gestiscono e curano un pool di indirizzi IP per ogni voce DNS. Quando un client effettua una richiesta, ad esempio a www.google.com, il DNS di Google seleziona uno degli indirizzi disponibili dal pool e lo invia al client. La strategia più semplice per inviare indirizzi IP consiste nell'usare una coda round robin in cui, dopo ogni risposta DNS, l'elenco di indirizzi viene permutato.
Prima dell'avvento del cloud, il bilanciamento del carico DNS era un modo semplice per ridurre la latenza delle connessioni a lunga distanza. Il dispatcher nel server DNS era programmato per rispondere con l'indirizzo IP del server geograficamente più vicino al client. Il modo più semplice per farlo era rispondere con l'indirizzo IP del pool numericamente il più vicino all'indirizzo IP del client. Questo metodo non era affidabile, perché gli indirizzi IP non vengono distribuiti in una gerarchia globale. Le tecniche attuali sono più sofisticate e si basano su un mapping software tra indirizzi IP e posizioni basato su mappe fisiche di provider di servizi Internet (ISP). Poiché questo mapping viene implementato come una costosa ricerca software, questo metodo produce risultati migliori, ma è costoso. Tuttavia, il costo di una ricerca lenta viene ammortizzato poiché la ricerca del DNS viene eseguita solo quando il client effettua la prima connessione a un server. Tutte le comunicazioni successive avvengono direttamente tra il client e il server a cui appartiene l'indirizzo IP inviato. Un esempio di schema di bilanciamento del carico DNS è illustrato nella Figura 9.
Figura 9: Bilanciamento del carico in un ambiente cloud.
Lo svantaggio di questo metodo è che, in caso di errore del server, il passaggio a un indirizzo IP diverso dipende dalla configurazione di durata (TTL) della cache DNS. È risaputo che le voci DNS sono di lunga durata e che la propagazione degli aggiornamenti richiede più di una settimana. Questo significa che è difficile "nascondere" rapidamente al client un errore del server. La riduzione della validità (TTL) di un indirizzo IP nella cache migliora la situazione a scapito delle prestazioni e aumentando il numero di ricerche.
Spesso, con bilanciamento del carico moderno ci si riferisce all'uso di un'istanza dedicata (o di una coppia di istanze) per inviare le richieste in ingresso ai server back-end. Per ogni richiesta in ingresso su una porta specificata, il servizio di bilanciamento del carico reindirizza il traffico a uno dei server back-end in base a una strategia di distribuzione. Così facendo, il servizio di bilanciamento del carico conserva i metadati della richiesta, incluse informazioni quali le intestazioni del protocollo applicativo, ad esempio le intestazioni HTTP. In questa situazione il problema delle informazioni non aggiornate non sussiste, perché ogni richiesta passa attraverso il servizio di bilanciamento del carico.
Anche se tutti i tipi di servizi di bilanciamento del carico di rete inoltreranno le richieste ai server back-end insieme a qualunque contesto, quando si tratta di restituire la risposta al client, possono impiegare una delle due strategie di base1:
Intermediazione (proxy) - In questo approccio, il servizio di bilanciamento del carico riceve la risposta dal back-end e la inoltra al client. Il servizio di bilanciamento del carico si comporta come un proxy Web standard ed è coinvolto in entrambe le fasi di una transazione di rete, ovvero l'inoltro della richiesta al client e la restituzione della risposta.
Handoff TCP - In questo approccio, la connessione TCP con il client viene passata al server back-end e il server invia la risposta direttamente al client senza passare attraverso il servizio di bilanciamento del carico.
Quest'ultima strategia è illustrata nella Figura 10.
Figura 10: Meccanismo di handoff TCP dal dispatcher al server back-end.
Vantaggi del bilanciamento del carico
Uno dei vantaggi del bilanciamento del carico è che aiuta a mascherare gli errori in un sistema. Se il client è esposto a un unico endpoint che rappresenta più risorse, gli errori nelle singole risorse vengono nascosti al client servendo le richieste con altre risorse. Il servizio di bilanciamento del carico stesso, tuttavia, diventa un singolo punto di guasto. Se per qualsiasi motivo va in errore, anche se tutti i server back-end continuano a funzionare, non verrà elaborata alcuna richiesta client. Di conseguenza, spesso per ottenere la disponibilità elevata i servizi di bilanciamento del carico vengono implementati in coppia.
Ancora più importante, il bilanciamento del carico migliora la velocità di risposta distribuendo i carichi di lavoro tra più risorse di calcolo nel cloud. L'uso di un'unica istanza di calcolo nel cloud comporta diverse limitazioni. Nei moduli precedenti si è discusso della limitazione fisica delle prestazioni, laddove sono necessarie maggiori risorse per affrontare l'aumento dei carichi di lavoro. Usando il bilanciamento del carico, i carichi di lavoro più grandi vengono distribuiti tra più risorse. Ognuna di esse potrà soddisfare le richieste di sua pertinenza in modo indipendente e in parallelo, migliorando la velocità effettiva dell'applicazione. Il bilanciamento del carico migliora anche i tempi di risposta medi, perché sono presenti più server per gestire il carico di lavoro.
I controlli di integrità sono fondamentali per l'implementazione di strategie di bilanciamento del carico efficaci. Un servizio di bilanciamento del carico deve essere in grado di rilevare quando una risorsa diventa non disponibile, in modo da evitare di inoltrarvi traffico. Il monitoraggio Ping Echo, in cui il servizio di bilanciamento del carico effettua il ping sui server con richieste ICMP (Internet Control Message Protocol), è una delle tattiche più diffuse per la verifica dell'integrità di risorse specifiche. Oltre a considerare l'integrità di una risorsa quando si inoltra il traffico verso di essa, alcune strategie di bilanciamento del carico tengono conto anche di altre metriche, ad esempio la velocità effettiva, la latenza e l'utilizzo della CPU.
I servizi di bilanciamento del carico devono spesso garantire una disponibilità elevata. Il modo più semplice per farlo è creare più istanze di bilanciamento del carico (ciascuna con un indirizzo IP univoco) e collegarle a un solo indirizzo DNS. Ogni volta che, per qualsiasi motivo, un servizio di bilanciamento del carico riscontra un errore, viene sostituito con un altro e tutto il traffico viene passato all'istanza di failover, con un impatto minimo sulle prestazioni. Simultaneamente, è possibile configurare una nuova istanza del servizio di bilanciamento del carico per sostituire quella in errore e occorre aggiornare immediatamente i record DNS.
Oltre a distribuire le richieste tra i server back-end, i servizi di bilanciamento del carico usano spesso meccanismi per ridurre il carico sui server e migliorare la velocità effettiva complessiva. Ecco alcuni di questi meccanismi:
Offload SSL - Le connessioni HTTPS comportano un costo aggiuntivo in termini di prestazioni perché il traffico viene crittografato. Anziché soddisfare tutte le richieste tramite SSL (Secure Sockets Layer), la connessione client al servizio di bilanciamento del carico può essere effettuata tramite SSL, mentre le richieste di reindirizzamento a ogni singolo vengono effettuate tramite HTTP non crittografato. Questa tecnica riduce considerevolmente il carico sui server. Inoltre, la sicurezza viene mantenuta purché le richieste di reindirizzamento non vengano eseguite su una rete aperta.
Buffering TCP - Una strategia per l'offload dei client con connessioni lente al servizio di bilanciamento del carico, per alleggerire i server che gestiscono le risposte a tali client.
Memorizzazione nella cache - In alcuni scenari, il servizio di bilanciamento del carico può mantenere una cache per le richieste più popolari (o per le richieste che possono essere gestite senza passare per i server, come il contenuto statico), per ridurre il carico sui server.
Traffic shaping - Un servizio di bilanciamento del carico può usare questa tecnica per ritardare o ridefinire la priorità del flusso di pacchetti allo scopo di ottimizzare il traffico in base alla configurazione dei server. L'operazione influisce sulla qualità del servizio (QoS) per alcune richieste, ma assicura la capacità di gestire il carico in ingresso.
È importante ricordare che il bilanciamento del carico funziona solo se il servizio di bilanciamento del carico non è sottoposto a un carico insostenibile. In caso contrario, il servizio di bilanciamento del carico stesso diventa il collo di bottiglia. Fortunatamente, i servizi di bilanciamento del carico tendono a eseguire operazioni di elaborazione minime sulle richieste ricevute, affidandosi ai server back-end per eseguire il lavoro effettivo di trasformare le richieste in risposte.
Equa distribuzione
Nel cloud vengono usate diverse strategie di bilanciamento del carico. Una delle più comuni è l'equa distribuzione, che usa un semplice algoritmo round robin per distribuire il traffico in modo uniforme tra tutti i nodi. Non prende in considerazione l'utilizzo delle singole risorse nel sistema, né il tempo di esecuzione delle richieste. Questo approccio tenta di mantenere impegnato ogni nodo del sistema ed è uno dei più semplici da implementare.
AWS usa questo approccio nell'offerta Elastic Load Balancer (ELB). ELB effettua il provisioning di servizi di bilanciamento del carico che bilanciano il traffico tra istanze di EC2 collegate. I servizi di bilanciamento del carico sono essenzialmente istanze di EC2 con uno specifico servizio per l'instradamento del traffico. Quando il numero di istanze delle risorse dietro il servizio di bilanciamento del carico viene aumentato, gli indirizzi IP delle nuove risorse vengono aggiornati nel record DNS del servizio di bilanciamento del carico. Il completamento di questo processo richiede diversi minuti, perché comporta un tempo di monitoraggio e un tempo di provisioning. Questo periodo, ovvero il tempo di attesa finché il servizio di bilanciamento del carico non è pronto per gestire il carico superiore, è detto "riscaldamento" del servizio di bilanciamento del carico.
I servizi di bilanciamento del carico di AWS monitorano anche l'integrità delle risorse ad essi collegate per la distribuzione del carico di lavoro. Viene usato un meccanismo Ping Echo per verificare che tutte le risorse siano integre. Gli utenti di ELB possono configurare i parametri del controllo integrità specificando i ritardi e il numero di tentativi.
Distribuzione basata su hash
Questo approccio prova a garantire che le richieste dello stesso client per la durata di una sessione vengano indirizzate ogni volta allo stesso server, eseguendo l'hashing dei metadati che definiscono ogni richiesta e usando l'hash per selezionare un server. Se l'hashing viene eseguito correttamente, le richieste vengono distribuite in modo relativamente uniforme tra i server. Uno dei vantaggi di questo approccio è che si presta alle applicazioni in grado di riconoscere le sessioni, che possono archiviare i dati di sessione in memoria anziché scriverli in un archivio dati condiviso, ad esempio un database o una cache Redis. Uno svantaggio è che occorre eseguire l'hashing di ogni richiesta, il che introduce una piccola quantità di latenza.
Azure Load Balancer usa un meccanismo basato su hash per distribuire i carichi. Questo meccanismo crea un hash per ogni richiesta basato su IP di origine, porta di origine, IP di destinazione, porta di destinazione e tipo di protocollo, per garantire che in circostanze normali ogni pacchetto della stessa sessione raggiunga lo stesso server back-end. La funzione hash viene scelta in modo che la distribuzione delle connessioni ai server sia casuale.
Altre strategie di bilanciamento del carico
Se un particolare server sta bloccando l'elaborazione di una richiesta o di un set di richieste, i servizi di bilanciamento del carico che usano algoritmi di invio basati su hash o round robin invieranno comunque richieste verso di esso. Esistono altre strategie per il bilanciamento dei carichi tra più risorse, più sofisticate, che prendono in considerazione la capacità. Due delle metriche usate più di frequente per la misurazione della capacità sono:
Tempo di esecuzione delle richieste - Le strategie basate su questa metrica usano un algoritmo di pianificazione della priorità, in cui i tempi di esecuzione delle richieste vengono usati per scegliere la destinazione per le singole richieste. La sfida principale nell'uso di questo approccio è quella di misurare accuratamente i tempi di esecuzione. Un servizio di bilanciamento del carico può indovinare i tempi di esecuzione usando (e aggiornando costantemente) una tabella in memoria che archivia le differenze tra il momento in cui una richiesta viene trasmessa a ogni server e il momento in cui viene restituita.
Utilizzo delle risorse - Le strategie basate su questa metrica si servono dei valori di utilizzo della CPU per bilanciare l'utilizzo tra i nodi. Il servizio di bilanciamento del carico gestisce un elenco ordinato di risorse in base al relativo utilizzo e indirizza ogni richiesta ricevuta alla risorsa che in quel momento ha il carico minore.
Il bilanciamento del carico è fondamentale per l'implementazione di servizi cloud scalabili. In mancanza di un mezzo efficace per distribuire il traffico tra le risorse back-end, l'elasticità ottenuta creando risorse quando sono necessarie ed eseguendone il deprovisioning quando non lo sono più risulta fortemente limitata.
Riferimenti
- Aron Mohit, Darren Sanders, Peter Druschel e Willy Zwaenepoel (2000). "Distribuzione di richieste con supporto di contenuto scalabile nei server di rete basati su cluster". Documentazione della conferenza tecnica USENIX annuale 2000.