Leggere in inglese

Condividi tramite


Introduzione allo sviluppo di app resilienti

La resilienza è la capacità di un'app di eseguire il ripristino da errori temporanei e continuare a funzionare. Nel contesto della programmazione .NET, la resilienza viene ottenuta progettando app in grado di gestire correttamente gli errori e ripristinarsi rapidamente. Per creare app resilienti in .NET, in NuGet sono disponibili i due pacchetti seguenti:

Pacchetto NuGet Descrizione
📦 Microsoft.Extensions.Resilience Questo pacchetto NuGet fornisce meccanismi per rafforzare la protezione avanzata delle app in caso di errori temporanei.
📦 Microsoft.Extensions.Http.Resilience Questo pacchetto NuGet fornisce meccanismi di resilienza specifici per la classe HttpClient.

Questi due pacchetti NuGet sono basati su Polly, un progetto open source molto diffuso. Polly è una libreria di resilienza .NET e di gestione degli errori temporanei che consente agli sviluppatori di esprimere strategie come retry, interruttore, timeout, isolamento bulkhead, limite di frequenza, fallback e hedging in modo fluente e thread-safe.

Importante

Il pacchetto NuGet Microsoft.Extensions.Http.Polly è deprecato. Usare invece uno dei pacchetti indicati in precedenza.

Attività iniziali

Per iniziare a usare la resilienza in .NET, installare il pacchetto NuGet Microsoft.Extensions.Resilience.

dotnet add package Microsoft.Extensions.Resilience --version 8.0.0

Per altre informazioni, vedi dotnet add package o Gestire le dipendenze dei pacchetti nelle applicazioni .NET.

Creare una pipeline di resilienza

Per usare la resilienza, è prima necessario creare una pipeline di strategie basate sulla resilienza. Ogni strategia configurata viene eseguita in ordine di configurazione. In altre parole, l'ordine è importante. Il punto di ingresso è un metodo di estensione sul tipo IServiceCollection, denominato AddResiliencePipeline. Questo metodo accetta un identificatore della pipeline e un delegato che configura la pipeline. Il delegato viene passato a un'istanza di ResiliencePipelineBuilder, che viene usata per aggiungere strategie di resilienza alla pipeline.

Si consideri l'esempio basato su stringa seguente key:

using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.CircuitBreaker;
using Polly.Registry;
using Polly.Retry;
using Polly.Timeout;

var services = new ServiceCollection();

const string key = "Retry-Timeout";

services.AddResiliencePipeline(key, static builder =>
{
    // See: https://www.pollydocs.org/strategies/retry.html
    builder.AddRetry(new RetryStrategyOptions
    {
        ShouldHandle = new PredicateBuilder().Handle<TimeoutRejectedException>()
    });

    // See: https://www.pollydocs.org/strategies/timeout.html
    builder.AddTimeout(TimeSpan.FromSeconds(1.5));
});

Il codice precedente:

  • Crea una nuova istanza di ServiceCollection.
  • Definisce un key per identificare la pipeline.
  • Aggiunge una pipeline di resilienza all'istanza ServiceCollection.
  • Configura la pipeline con strategie di retry e timeout.

Ogni pipeline è configurata per un determinato key e ogni key viene usato per identificare il corrispondente ResiliencePipeline, quando si ottiene la pipeline dal provider. key è un parametro di tipo generico del metodo AddResiliencePipeline.

Estensioni del generatore di pipeline con resilienza

Per aggiungere una strategia alla pipeline, chiamare uno dei metodi di estensione disponibili Add* nell'istanza ResiliencePipelineBuilder.

  • AddRetry: riprovare se si verifica un errore, utile quando il problema è temporaneo e potrebbe risolversi
  • AddCircuitBreaker: interrompere i tentativi se qualcosa è rotto o occupato, il che avvantaggia l'utente, evitando di perdere tempo e di peggiorare le cose.
  • AddTimeout: rinunciare se un elemento richiede troppo tempo, il che può migliorare le prestazioni liberando le risorse.
  • AddRateLimiter: limitare il numero di richieste accettate, il che consente di controllare il carico in ingresso.
  • AddConcurrencyLimiter: limitare il numero di richieste effettuate, il che consente di controllare il carico in uscita.
  • AddFallback: eseguire altre operazioni quando si verificano errori, migliorando l'esperienza utente.
  • AddHedging: eseguire più richieste in caso di elevata latenza o errore, il che può migliorare la velocità di risposta.

Per altre informazioni, vedere Strategie di resilienza. Per gli esempi, vedere Creare app HTTP resilienti: modelli di sviluppo principali.

Arricchimento delle metriche

L'arricchimento è l'aumento automatico dei dati di telemetria con stato noto, sotto forma di coppie nome/valore. Ad esempio, un'app potrebbe generare un log che include l'operazione e il codice del risultato come colonne per rappresentare il risultato di un'operazione. In questa situazione e a seconda del contesto periferico, l'arricchimento aggiunge al registro il nome del cluster, il nome del processo, l'area geografica, l'ID tenant e altro, man mano che viene inviato al back-end di telemetria. Quando viene aggiunto l'arricchimento, il codice dell'app non deve eseguire alcuna operazione aggiuntiva per trarre vantaggio dalle metriche arricchite.

Funzionamento dell'arricchimento

Poniamo 1.000 istanze del servizio distribuite a livello globale che generano log e metriche. Quando si verifica un problema nel dashboard del servizio, è fondamentale identificare rapidamente l'area o il data center problematico. L'arricchimento garantisce che i record delle metriche contengano le informazioni necessarie per individuare gli errori nei sistemi distribuiti. Senza arricchimento, il carico di lavoro dipende dal codice dell'app per gestire internamente questo stato, integrarlo nel processo di registrazione e trasmetterlo manualmente. L'arricchimento semplifica questo processo, gestendolo senza influire sulla logica dell'app.

In caso di resilienza, quando si aggiunge l'arricchimento, le dimensioni seguenti vengono aggiunte ai dati di telemetria in uscita:

  • error.type: versione a cardinalità bassa delle informazioni di un'eccezione.
  • request.name: il nome della richiesta.
  • request.dependency.name: il nome della dipendenza.

Dietro le quinte, l'arricchimento della resilienza si basa sulla telemetria di Polly MeteringEnricher. Per altre informazioni, vedere Polly: Misurazione dell'arricchimento.

Aggiungere l'arricchimento con resilienza

Oltre a registrare una pipeline di resilienza, è anche possibile registrare l'arricchimento con resilienza. Per aggiungere l'arricchimento, chiamare il metodo extensions AddResilienceEnricher(IServiceCollection) nell'istanza IServiceCollection.

services.AddResilienceEnricher();

Chiamando il metodo di estensione AddResilienceEnricher, si aggiungono dimensioni sopra quelle predefinite incorporate nella libreria Polly sottostante. Vengono aggiunte le dimensioni di arricchimento seguenti:

Usare la pipeline di resilienza

Per usare una pipeline di resilienza configurata, è necessario ottenere la pipeline da un oggetto ResiliencePipelineProvider<TKey>. Quando è stata aggiunta la pipeline in precedenza, key era di tipo string, quindi è necessario ottenere la pipeline da ResiliencePipelineProvider<string>.

using ServiceProvider provider = services.BuildServiceProvider();

ResiliencePipelineProvider<string> pipelineProvider =
    provider.GetRequiredService<ResiliencePipelineProvider<string>>();

ResiliencePipeline pipeline = pipelineProvider.GetPipeline(key);

Il codice precedente:

  • Compila un oggetto ServiceProvider dall'istanza ServiceCollection.
  • Ottiene l'oggetto ResiliencePipelineProvider<string> dal provider di servizi.
  • Recupera l'oggetto ResiliencePipeline da ResiliencePipelineProvider<string>.

Eseguire la pipeline di resilienza

Per usare la pipeline di resilienza, chiamare uno dei metodi disponibili Execute* nell'istanza ResiliencePipeline. Si consideri ad esempio una chiamata al metodo ExecuteAsync:

await pipeline.ExecuteAsync(static cancellationToken =>
{
    // Code that could potentially fail.

    return ValueTask.CompletedTask;
});

Il codice precedente esegue il delegato all'interno del metodo ExecuteAsync. In caso di errori, vengono eseguite le strategie configurate. Ad esempio, se RetryStrategy è configurato per riprovare tre volte, il delegato viene eseguito quattro volte (un tentativo iniziale più tre tentativi), prima della propagazione dell'errore.

Passaggi successivi