Resilienza dell'applicazione e dell'infrastruttura
La resilienza è la capacità di recupero da errori temporanei. La strategia di recupero dell'app ripristina la funzione normale con un impatto minimo sull'utente. Gli errori possono verificarsi in ambienti cloud e l'app deve rispondere in modo da ridurre al minimo i tempi di inattività e la perdita di dati. In una situazione ideale, l'app gestisce correttamente gli errori senza che l'utente sappia mai che si è verificato un problema.
Poiché gli ambienti di microservizi possono essere volatili, progettare le app per aspettarsi e gestire errori parziali. Un errore parziale, ad esempio, può includere eccezioni di codice, interruzioni di rete, processi del server non risponde o errori hardware. Anche le attività pianificate, ad esempio lo spostamento di contenitori in un nodo diverso all'interno di un cluster Kubernetes, possono causare un errore temporaneo.
Approcci alla resilienza
Nella progettazione di applicazioni resilienti, spesso è necessario scegliere tra un calo rapido e normale. Errore rapido indica che l'applicazione genererà immediatamente un errore o un'eccezione quando si verifica un errore, anziché tentare di recuperare o risolvere il problema. Ciò consente di identificare e risolvere rapidamente i problemi. Riduzione delle prestazioni normale significa che l'applicazione tenterà di mantenere il funzionamento in una capacità limitata anche quando un componente non riesce.
Nelle applicazioni native del cloud è importante che i servizi gestisca correttamente gli errori anziché non si interrompano rapidamente. Poiché i microservizi sono decentralizzati e distribuibili in modo indipendente, sono previsti errori parziali. Se si verifica un errore rapido, si verifica un errore in un servizio per ridurre rapidamente i servizi dipendenti, riducendo così la resilienza complessiva del sistema. I microservizi devono invece essere codificati per prevedere e tollerare errori interni ed esterni del servizio. Questa normale riduzione delle prestazioni consente al sistema complessivo di continuare a funzionare anche se alcuni servizi vengono interrotti. Le funzioni critiche rivolte all'utente possono essere sostenute, evitando un'interruzione completa. Un errore normale consente anche ai servizi disturbati di recuperare o guarire automaticamente prima di influire sul resto del sistema. Pertanto, per le applicazioni basate su microservizi, una riduzione graduale delle prestazioni è più allineata alle procedure consigliate per la resilienza, ad esempio l'isolamento degli errori e il ripristino rapido. Impedisce agli eventi imprevisti locali di propagazione nel sistema.
Esistono due approcci fondamentali per supportare una riduzione normale delle prestazioni con resilienza: applicazione e infrastruttura. Ogni approccio presenta pro e contro. Entrambi gli approcci possono essere appropriati a seconda della situazione. Questo modulo illustra come implementare la resilienza basata sul codice e basata sull'infrastruttura.
Resilienza basata sul codice
Per implementare la resilienza basata su codice, .NET dispone di una libreria di estensioni per la resilienza e la gestione degli errori temporanei, Microsoft.Extensions.Http.Resilience
.
Questa libreria utilizza una sintassi intuitiva e facile da comprendere per compilare il codice di gestione degli errori in modalità thread-safe. Il comportamento di gestione degli errori è definito da diversi criteri di resilienza. In questo modulo vengono applicate le strategie di ripetizione dei tentativi e interruttore alle operazioni client HTTP.
Strategia di ripetizione dei tentativi
Una strategia di ripetizione dei tentativi è esattamente ciò che implica il nome. Nel caso di una risposta di errore, la richiesta viene ripetuta dopo una breve tempo di attesa. Il tempo di attesa aumenta con ogni tentativo. L'aumento può essere lineare o esponenziale.
Dopo il raggiungimento del numero massimo di tentativi, la strategia restituisce e genera un'eccezione. Dal punto di vista dell'utente, l'app richiede in genere più tempo per il completamento di alcune operazioni. L'app potrebbe anche far trascorrere un po' di tempo, prima che l'utente venga informato che non è stato possibile completare l'operazione.
Strategia dell'interruttore
Una strategia di interruttore fornisce a un servizio di destinazione un'interruzione dopo un numero ripetuto di errori sospendo il tentativo di comunicare con esso. Il servizio potrebbe riscontrare un problema grave e non essere temporaneamente in grado di rispondere. Dopo un numero definito di errori consecutivi, i tentativi di connessione vengono sospesi, aprendo il circuito. Durante questa attesa, le operazioni aggiuntive sul servizio di destinazione hanno esito negativo immediatamente senza nemmeno tentare di connettere il servizio. Una volta trascorso il tempo di attesa, viene effettuato un nuovo tentativo. Se il servizio risponde correttamente, il circuito viene chiuso e il sistema torna normale.
Resilienza basata sull'infrastruttura
Per implementare la resilienza basata sull'infrastruttura, è possibile usare una mesh di servizi. Oltre alla resilienza senza modifica del codice, una mesh di servizi offre vantaggi in termini di gestione del traffico, criteri, sicurezza, identità consolidata e osservabilità. L'app viene dissociata da queste funzionalità operative, che vengono spostate al livello di infrastruttura.
Confronto con gli approcci basati sul codice
Un approccio basato sull'infrastruttura di resilienza può usare una vista basata su metriche che consente di adattarsi dinamicamente alle condizioni del cluster in tempo reale. Questo approccio aggiunge un'altra dimensione alla gestione del cluster, ma non aggiunge codice.
Con un approccio basato su codice, è possibile:
- Individuare i parametri appropriati di ripetizione dei tentativi e timeout.
- Concentrarsi su una richiesta HTTP specifica.
Non esiste un modo ragionevole di rispondere a un errore dell'infrastruttura nel codice dell'app. Se si considerano le centinaia o migliaia di richieste elaborate simultaneamente, anche un tentativo con backoff esponenziale (moltiplicato per il numero di richieste) può sovraccaricare un servizio.
Al contrario, gli approcci basati sull'infrastruttura non sono a conoscenza degli elementi interni dell'app. Ad esempio, le transazioni di database complesse sono invisibili alle mesh di servizi. Tali transazioni possono essere protette solo da errori con un approccio basato su codice.
Nelle prossime unità si implementerà la resilienza per un'app basata su microservizi usando la resilienza HTTP .NET nel codice e una mesh di servizi Linkerd.