Progettazione per la correzione automatica
Progettare l'applicazione in modo che sia in grado di eseguire la correzione automatica dei propri errori
In un sistema distribuito, ci si deve aspettare che si verifichino degli errori. L'hardware può guastarsi. La rete, poi, può avere errori temporanei. Raramente un intero servizio, un data center o anche un'area di Azure subisce un'interruzione, ma è comunque necessario prevederla nell'architettura del carico di lavoro. La resilienza e il ripristino devono essere affrontati nelle fasi iniziali della progettazione del carico di lavoro.
A tal fine, è consigliabile progettare un'applicazione che sia in grado di eseguire la correzione automatica dei propri errori. Ciò richiede un approccio a tre fasi:
- Rilevamento degli errori.
- Risposta graduale agli errori.
- Acquisizione di informazioni operative tramite la registrazione e il monitoraggio degli errori.
La modalità di risposta a un determinato tipo di errore dipende dai requisiti di disponibilità dell'applicazione. Ad esempio, se è necessaria la disponibilità elevata, è possibile eseguire la distribuzione in più zone di disponibilità in un'area. Per evitare interruzioni, anche nel caso improbabile in cui si verifichino in un'intera area di Azure, è possibile eseguire automaticamente il failover in un'area secondaria durante un'interruzione a livello di area. Ciò tuttavia comporta un costo superiore e prestazioni potenzialmente inferiori rispetto alla distribuzione in una singola area.
È bene inoltre non valutare solo eventi importanti come le interruzioni a livello di area, in genere rare. È consigliabile invece concentrarsi sulla gestione degli errori locali e di breve durata, ad esempio gli errori di connettività di rete o le connessioni di database non riuscite.
Consigli
Usare componenti disaccoppiati che comunicano in modo asincrono. Idealmente, i componenti sono disaccoppiati in termini di tempo e spazio. Disaccoppiati nel tempo significa che i componenti non devono essere presenti contemporaneamente per consentire la comunicazione. Disaccoppiati nello spazio significa che il mittente e il ricevitore non devono essere eseguiti nello stesso processo, ma possono trovarsi ovunque siano più efficienti. Per comunicare tra loro, i componenti disaccoppiati usano idealmente eventi. Ciò consente di ridurre al minimo la probabilità di errori a catena.
Ritentare le operazioni non riuscite. Gli errori temporanei potrebbero dipendere da una perdita momentanea della connettività di rete, da una connessione a un database eliminato o da un timeout che si verifica quando un servizio è occupato. Per gestire gli errori temporanei, nell'applicazione va prevista una logica di ripetizione dei tentativi. In molti servizi di Azure, l'SDK client implementa i tentativi automatici. Per altre informazioni, vedere Gestione degli errori temporanei e Modello di ripetizione dei tentativi.
Proteggere dagli errori i servizi remoti (Interruttore). Effettuare un tentativo dopo un errore temporaneo è un metodo valido, ma se il problema persiste può accadere che si ricevano segnalazioni di malfunzionamento del servizio da un numero eccessivo di chiamanti. Il sommarsi delle richieste può innescare errori a catena. Usare il modello a interruttore per fallire e rispondere immediatamente agli errori, senza chiamata remota, quando un'operazione avrà probabilmente esito negativo.
Isolare le risorse critiche (A scomparti). Gli errori in un sottosistema possono propagarsi. Questa situazione può verificarsi se un errore fa sì che alcune risorse, come i thread o i socket, non vengano liberate in modo tempestivo, causandone l'esaurimento. Per evitare questo problema, usare il Modello a scomparti per partizionare un sistema in gruppi isolati, in modo che un errore in una partizione non causi l'arresto dell'intero sistema.
Adottare il livellamento del carico. Nelle applicazioni potrebbero verificarsi picchi improvvisi di traffico che sovraccaricano i servizi nel back-end. Per evitare questo problema, usare lo schema di livellamento del carico basato sulle code per inserire in coda gli elementi di lavoro per l'esecuzione asincrona. La coda si comporta come un buffer che contiene i picchi di carico.
Effettuare il failover. Se un'istanza non è raggiungibile, effettuare il failover su un'altra istanza. Per gli elementi senza stato, ad esempio un server Web, inserire più istanze dietro a un servizio di bilanciamento del carico o di gestione traffico. Per elementi che archiviano lo stato, ad esempio un database, usare le repliche e il failover. A seconda dell'archivio dati e della relativa modalità di replica, l'applicazione potrebbe dover gestire la coerenza.
Compensare le transazioni non riuscite. In generale è consigliabile evitare le transazioni distribuite, in quanto richiedono il coordinamento tra servizi e risorse. In alternativa, creare un'operazione costituita da singole transazioni più piccole. Se l'operazione si interrompe prima del termine, usare il modello Transazioni di compensazione per annullare qualsiasi passaggio già completato.
Eseguire il checkpoint delle transazioni con esecuzione prolungata. I checkpoint possono fornire resilienza qualora un'operazione con esecuzione prolungata non riesca. Quando l'operazione si riavvia (ad esempio, viene prelevata da un'altra macchina virtuale), può essere ripresa a partire dall'ultimo checkpoint. Prendere in considerazione l'implementazione di un meccanismo di checkpoint che registri informazioni sullo stato dell'attività a intervalli regolari e salvi lo stato nell'archivio permanente accessibile da qualsiasi istanza del processo che esegue l'attività. In questo modo, se il processo viene arrestato, il lavoro che stava eseguendo può essere ripreso a partire dall'ultimo checkpoint mediante un'altra istanza. Sono disponibili librerie che forniscono questa funzionalità, ad esempio NServiceBus e MassTransit. Mantengono in modo trasparente lo stato di persistenza, in cui gli intervalli sono allineati all'elaborazione dei messaggi provenienti dalle code nel bus di servizio di Azure.
Ridurre le prestazioni in modo graduale e rimanere reattivi durante l'errore. In alcuni casi non è possibile risolvere un problema, ma è può risultare comunque utile offrire funzionalità ridotte. Si consideri un'applicazione in cui viene visualizzato un catalogo di libri. Nel caso in cui l'applicazione non possa recuperare l'immagine di anteprima di una copertina, si potrebbe visualizzare un'immagine segnaposto. Interi sottosistemi possono non essere critici per l'applicazione. Ad esempio, su un sito di e-commerce la visualizzazione dei consigli sui prodotti è probabilmente meno importante dell'elaborazione degli ordini.
Limitare i client. A volte un numero ridotto di utenti crea un carico eccessivo che può ridurre la disponibilità dell'applicazione per gli altri utenti. In questo caso, è utile limitare il client per un determinato periodo di tempo. Vedere Modello Limitazione.
Bloccare gli utenti malintenzionati. Applicare una limitazione a un client non implica necessariamente che questi abbia agito con cattive intenzioni. Significa semplicemente che ha superato la propria quota del servizio. Tuttavia, se un client supera spesso la propria quota o mostra un comportamento errato, è possibile bloccarlo. Definire un processo fuori banda affinché l'utente possa richiedere lo sblocco.
Usare il modello Designazione leader. Quando è necessario coordinare un'attività, usare il modello Designazione leader per selezionare un coordinatore. In questo modo, il coordinatore non rappresenta un singolo punto di guasto. In caso di errore, ne viene selezionato un altro. Se non si vuole implementare un algoritmo di designazione leader da zero, è possibile prendere in considerazione una soluzione preconfigurata, ad esempio Zookeeper.
Eseguire i test di inserimento degli errori. Accade spesso che venga testato il percorso di riuscita ma non il percorso di errore. Un sistema può essere eseguito nell'ambiente di produzione per lungo tempo prima che venga rilevato un percorso di errore. Usare i test di inserimento degli errori per testare la resilienza del sistema sia attivando errori reali sia simulandoli.
Adottare l'ingegneria del caos. L'ingegneria del caos estende il concetto di inserimento degli errori inserendo nelle istanze di produzione errori o condizioni anormali in modo casuale.
Usare le zone di disponibilità. Molte aree di Azure forniscono zone di disponibilità, che sono set isolati di data center all'interno dell'area. Alcuni servizi di Azure possono essere distribuiti a livello di zona, il che fa sì vengano posizionati in una zona specifica e possano ridurre la latenza nella comunicazione tra componenti nello stesso carico di lavoro. In alternativa, alcuni servizi possono essere distribuiti con ridondanza della zona, il che significa che Azure replica automaticamente la risorsa tra zone diverse per garantire la disponibilità elevata. Prendere in considerazione l'approccio che offre il miglior set di compromessi per la soluzione. Per altre informazioni su come progettare la soluzione per l'uso di zone e di disponibilità e aree, vedere Raccomandazioni per l'uso di zone di disponibilità e aree.
Per un approccio strutturato a come creare applicazioni che siano in grado di eseguire la correzione automatica dei propri errori, vedere Progettazione di applicazioni resilienti per Azure.