Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Suggerimento
Questo contenuto è un estratto dell'eBook, Architettura di microservizi .NET per applicazioni .NET containerizzati, disponibile in documentazione .NET o come PDF scaricabile gratuitamente leggibile offline.
Come indicato in precedenza, è consigliabile gestire gli errori che potrebbero richiedere una quantità variabile di tempo per il ripristino, come potrebbe verificarsi quando si tenta di connettersi a un servizio remoto o a una risorsa. La gestione di questo tipo di errore può migliorare la stabilità e la resilienza di un'applicazione.
In un ambiente distribuito, le chiamate a risorse e servizi remoti possono non riuscire a causa di errori temporanei, ad esempio connessioni di rete lente e timeout, oppure se le risorse rispondono lentamente o sono temporaneamente non disponibili. Questi errori vengono in genere corretti dopo un breve periodo di tempo e un'applicazione cloud affidabile deve essere preparata per gestirli usando una strategia come il "modello di ripetizione dei tentativi".
Tuttavia, possono verificarsi situazioni in cui gli errori sono dovuti a eventi imprevisti che potrebbero richiedere molto più tempo per la correzione. Questi errori possono variare, in base alla gravità, dalla perdita parziale della connettività alla totale interruzione di un servizio. In queste situazioni, potrebbe non essere opportuno che un'applicazione riprova continuamente a eseguire un'operazione che non è probabile che abbia esito positivo.
L'applicazione deve invece essere codificata per accettare che l'operazione non sia riuscita e gestire di conseguenza l'errore.
L'uso di tentativi di ripetizione Http senza attenzione potrebbe comportare la creazione di un attacco di Denial of Service (DoS) all'interno del proprio software. Poiché un microservizio ha esito negativo o viene eseguito lentamente, più client potrebbero ripetere ripetutamente le richieste non riuscite. Ciò crea un rischio pericoloso di aumentare in modo esponenziale il traffico destinato al servizio in errore.
Pertanto, è necessario un certo tipo di barriera di difesa in modo che le richieste eccessive si arrestino quando non vale la pena continuare a provare. Questa barriera di difesa è esattamente l'interruttore.
Il pattern Circuit Breaker ha uno scopo diverso rispetto al pattern Retry. Il "modello di ripetizione dei tentativi" consente a un'applicazione di ripetere un'operazione nell'attesa che l'operazione abbia esito positivo. Il modello interruttore impedisce a un'applicazione di eseguire un'operazione che potrebbe non riuscire. Un'applicazione può combinare questi due modelli. Tuttavia, la logica di ripetizione dei tentativi deve essere sensibile a qualsiasi eccezione restituita dall'interruttore e deve abbandonare i tentativi se l'interruttore indica che un errore non è temporaneo.
Implementare il pattern Circuit Breaker con IHttpClientFactory e Polly
Come nell'implementazione dei meccanismi di ripetizione, l'approccio consigliato per i circuit breaker è sfruttare librerie .NET consolidate come Polly e la sua integrazione nativa con IHttpClientFactory.
L'integrazione di una politica di interruttore automatico nella IHttpClientFactory pipeline middleware in uscita è semplice, come aggiungere un singolo pezzo di codice incrementale a quanto già esistente quando si utilizza IHttpClientFactory.
L'unica aggiunta qui al codice usato per i tentativi di chiamata HTTP è il codice in cui si aggiungono i criteri di Circuit Breaker all'elenco dei criteri utilizzati, come illustrato nel seguente codice incrementale.
// Program.cs
var retryPolicy = GetRetryPolicy();
var circuitBreakerPolicy = GetCircuitBreakerPolicy();
builder.Services.AddHttpClient<IBasketService, BasketService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5)) // Sample: default lifetime is 2 minutes
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddPolicyHandler(retryPolicy)
.AddPolicyHandler(circuitBreakerPolicy);
Il AddPolicyHandler() metodo è ciò che aggiunge criteri agli HttpClient oggetti che verranno usati. In questo caso, viene aggiunto un criterio Polly per un interruttore.
Per avere un approccio più modulare, la Politica di Circuit Breaker è definita in un metodo separato denominato GetCircuitBreakerPolicy(), come illustrato nel codice seguente.
// also in Program.cs
static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
}
Nell'esempio di codice precedente, i criteri dell'interruttore vengono configurati in modo da interrompere o aprire il circuito quando si sono verificati cinque errori consecutivi quando si ritentano le richieste HTTP. In questo caso, il circuito si interromperà per 30 secondi: in quel periodo, le chiamate verranno bloccate immediatamente dall'interruttore, anziché essere realmente effettuate. Il criterio interpreta automaticamente le eccezioni pertinenti e i codici di stato HTTP come errori.
I interruttore devono essere usati anche per reindirizzare le richieste a un'infrastruttura di fallback se si sono riscontrati problemi in una determinata risorsa distribuita in un ambiente diverso rispetto all'applicazione client o al servizio che esegue la chiamata HTTP. In questo modo, se si verifica un'interruzione nel data center che influisce solo sui microservizi back-end, ma non sulle applicazioni client, le applicazioni client possono reindirizzare ai servizi di fallback. Polly sta pianificando un nuovo criterio per automatizzare questo scenario di criterio di failover.
Tutte queste funzionalità sono per i casi in cui si gestisce il failover dall'interno del codice .NET, anziché gestirlo automaticamente da Azure, con trasparenza della posizione.
Dal punto di vista dell'utilizzo, quando si usa HttpClient, non è necessario aggiungere nulla di nuovo perché il codice è uguale a quando si usa HttpClient con IHttpClientFactory, come illustrato nelle sezioni precedenti.
Testare i tentativi Http e gli interruttori di circuito in eShopOnContainers
Ogni volta che si avvia la soluzione eShopOnContainers in un host Docker, è necessario avviare più contenitori. Alcuni contenitori sono più lenti per l'avvio e l'inizializzazione, ad esempio il contenitore di SQL Server. Ciò vale soprattutto la prima volta che si distribuisce l'applicazione eShopOnContainers in Docker perché deve configurare le immagini e il database. Il fatto che alcuni contenitori inizino più lentamente di altri può causare inizialmente che il resto dei servizi generi eccezioni HTTP, anche se si impostano dipendenze tra contenitori a livello di docker-compose, come illustrato nelle sezioni precedenti. Queste dipendenze docker-compose tra contenitori si trovano solo a livello di processo. Il processo del punto di ingresso del contenitore potrebbe essere avviato, ma SQL Server potrebbe non essere pronto per le query. Il risultato può essere una catena di errori e l'applicazione può ottenere un'eccezione quando si tenta di utilizzare tale contenitore specifico.
È anche possibile che questo tipo di errore venga visualizzato all'avvio quando l'applicazione viene distribuita nel cloud. In tal caso, gli agenti di orchestrazione potrebbero spostare i contenitori da un nodo o da una macchina virtuale a un'altra (ovvero l'avvio di nuove istanze) durante il bilanciamento del numero di contenitori tra i nodi del cluster.
Il modo in cui "eShopOnContainers" risolve questi problemi quando si avviano tutti i contenitori è usando il modello di ripetizione dei tentativi illustrato in precedenza.
Testare l'interruttore in eShopOnContainers
Esistono alcuni modi per interrompere/aprire il circuito e testarlo con eShopOnContainers.
Un'opzione consiste nel ridurre il numero di tentativi consentiti a 1 nei criteri di interruttore e ridistribuire l'intera soluzione in Docker. Con un singolo tentativo, è possibile che una richiesta HTTP non riesca durante la distribuzione, l'interruttore verrà aperto e si riceverà un errore.
Un'altra opzione consiste nell'usare il middleware personalizzato implementato nel microservizio Basket . Quando questo middleware è abilitato, intercetta tutte le richieste HTTP e restituisce il codice di stato 500. È possibile abilitare il middleware effettuando una richiesta GET all'URI che ha generato errori, come illustrato di seguito:
GET http://localhost:5103/failing
Questa richiesta restituisce lo stato corrente del middleware. Se il middleware è abilitato, la richiesta restituisce il codice di stato 500. Se il middleware è disabilitato, non viene restituita alcuna risposta.GET http://localhost:5103/failing?enable
Questa richiesta abilita il middleware.GET http://localhost:5103/failing?disable
Questa richiesta disabilita il middleware.
Ad esempio, quando l'applicazione è in esecuzione, è possibile abilitare il middleware effettuando una richiesta usando l'URI seguente in qualsiasi browser. Si noti che il microservizio di ordinamento usa la porta 5103.
http://localhost:5103/failing?enable
È quindi possibile controllare lo stato usando l'URI http://localhost:5103/failing, come illustrato nella figura 8-5.
Figura 8-5. Controllo dello stato del middleware ASP.NET "Non Funzionante": in questo caso, disabilitato.
A questo punto, il microservizio Basket risponde con il codice di stato 500 ogni volta che lo si invoca.
Quando il middleware è in esecuzione, è possibile provare a eseguire un ordine dall'applicazione Web MVC. Poiché le richieste hanno esito negativo, il circuito verrà aperto.
Nell'esempio seguente è possibile notare che l'applicazione Web MVC ha un blocco catch nella logica per l'inserimento di un ordine. Se il codice intercetta un'eccezione a circuito aperto, viene visualizzato un messaggio descrittivo che informa l'utente di attendere.
public class CartController : Controller
{
//…
public async Task<IActionResult> Index()
{
try
{
var user = _appUserParser.Parse(HttpContext.User);
//Http requests using the Typed Client (Service Agent)
var vm = await _basketSvc.GetBasket(user);
return View(vm);
}
catch (BrokenCircuitException)
{
// Catches error when Basket.api is in circuit-opened mode
HandleBrokenCircuitException();
}
return View();
}
private void HandleBrokenCircuitException()
{
TempData["BasketInoperativeMsg"] = "Basket Service is inoperative, please try later on. (Business message due to Circuit-Breaker)";
}
}
Ecco un riepilogo. Il criterio Ripetizione tentativi tenta più volte di effettuare la richiesta HTTP e ottiene errori HTTP. Quando il numero di tentativi raggiunge il massimo impostato per il criterio del Circuit Breaker (in questo caso, 5), l'applicazione genera un'eccezione BrokenCircuitException. Il risultato è un messaggio amichevole, come mostrato nella figura 8-6.
Figura 8-6. Interruttore che restituisce un errore all'interfaccia utente
È possibile implementare logica diversa per quando aprire/interrompere il circuito. In alternativa, è possibile provare una richiesta HTTP su un microservizio back-end diverso se è presente un data center di fallback o un sistema back-end ridondante.
Infine, un'altra possibilità per il CircuitBreakerPolicy è usare il Isolate (che forza e mantiene aperto il circuito) e il Reset (che lo richiude). Possono essere usati per compilare un endpoint HTTP dell'utilità che richiama Isolate e Reset direttamente nei criteri. Un endpoint HTTP di questo tipo può essere usato, adeguatamente protetto, nell'ambiente di produzione per isolare temporaneamente un sistema downstream, ad esempio quando si vuole aggiornarlo. Oppure potrebbe interrompere manualmente il circuito per proteggere un sistema a valle che si sospetta essere difettoso.
Risorse aggiuntive
-
Modello Circuit Breaker
https://learn.microsoft.com/azure/architecture/patterns/circuit-breaker