Resilienza dell'applicazione e dell'infrastruttura
La resilienza è la possibilità di eseguire il ripristino da errori temporanei. La strategia di ripristino dell'app ripristina la normale funzione 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 di resilienza
Nella progettazione di applicazioni resilienti, spesso è necessario scegliere tra un fallimento rapido e una degradazione graduale. Fallimento rapido indica che l'applicazione genererà immediatamente un errore o un'eccezione quando qualcosa va storto, anziché tentare di recuperare o risolvere il problema. Ciò consente di identificare e risolvere rapidamente i problemi. La graduale riduzione delle prestazioni significa che l'applicazione tenterà di mantenere il funzionamento con una capacità limitata anche quando un componente non riesce.
Nelle applicazioni native del cloud è importante che i servizi gestiscano correttamente gli errori anziché fallire rapidamente. Poiché i microservizi sono decentralizzati e distribuibili in modo indipendente, sono previsti errori parziali. Un errore rapido farebbe sì che un guasto in un servizio abbatta 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 guasto graduale consente inoltre ai servizi interessati dal problema di recuperare o ripararsi 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 vantaggi e svantaggi. 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 su 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.
Usa una sintassi fluente e facile da comprendere per compilare codice di gestione degli errori in modo thread-safe. Esistono diversi criteri di resilienza che definiscono il comportamento di gestione degli errori. In questo modulo si applicano le strategie di ripetizione dei tentativi e di interruzione alle operazioni dei client HTTP.
Strategia di ripetizione dei tentativi
Una strategia di ripetizione dei tentativi è esattamente ciò che implica il nome. La richiesta viene ritentata dopo un breve intervallo di attesa se viene ricevuta una risposta di errore. 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 completare alcune operazioni. L'app potrebbe anche richiedere del tempo prima di informare l'utente che non è riuscito a completare l'operazione.
Strategia del Circuit Breaker
Una strategia di circuit breaker fornisce a un servizio di destinazione una pausa dopo numerosi errori consecutivi, sospendendo 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 falliscono immediatamente senza nemmeno tentare di stabilire una connessione con il servizio. Dopo che è trascorso il tempo di attesa, l'operazione viene tentata di nuovo. 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 modificare il codice, una mesh di servizi offre gestione del traffico, criteri, sicurezza, identità avanzata e osservabilità. L'app viene disaccoppiata da queste funzionalità operative, che vengono spostate al livello dell'infrastruttura.
Confronto con approcci basati su 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:
- Sono necessari per indovinare quali parametri di ripetizione e timeout sono appropriati.
- Concentrarsi su una richiesta HTTP specifica.
Non esiste un modo ragionevole per rispondere a un errore dell'infrastruttura nel codice dell'app. Prendere in considerazione centinaia o migliaia di richieste elaborate contemporaneamente. 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.