Condividi tramite


Introduzione allo sviluppo di app resilienti

La resilienza è la capacità di un'app di riprendersi da guasti transitori e continuare a funzionare. Nel contesto della programmazione .NET, la resilienza si ottiene progettando app in grado di gestire i guasti con eleganza e recuperare rapidamente. Per aiutare a costruire app resilienti in .NET, i seguenti due pacchetti sono disponibili su NuGet:

Pacchetto NuGet Descrizione
📦 Microsoft.Extensions.Resilience Questo pacchetto NuGet fornisce meccanismi per proteggere le app contro i guasti transitori.
📦 Microsoft.Extensions.Http.Resilience Questo pacchetto NuGet fornisce meccanismi di resilienza specifici per la classe HttpClient.

Questi due pacchetti NuGet sono costruiti sopra Polly, che è un popolare progetto open-source. Polly è una libreria .NET per la resilienza e la gestione dei guasti transitori che permette agli sviluppatori di esprimere strategie come il retry, circuit breaker, timeout, bulkhead isolation, rate-limiting, fallback, e hedging in modo fluente e thread-safe.

Importante

Il pacchetto NuGet Microsoft.Extensions.Http.Polly è deprecato. Usa invece uno dei pacchetti menzionati.

Inizia subito

Per iniziare a utilizzare la resilienza in .NET, installa il pacchetto NuGet Microsoft.Extensions.Resilience.

dotnet add package Microsoft.Extensions.Resilience

Per altre informazioni, vedere dotnet package add or Manage package dependencies in .NET applications (Aggiungere o gestire le dipendenze dei pacchetti nelle applicazioni .NET).

Costruisci una pipeline di resilienza

Per utilizzare la resilienza, è necessario prima costruire un insieme 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, chiamato AddResiliencePipeline. Questo metodo prende un identificatore del pipeline e un delegato che configura il pipeline. Al delegato viene passata un'istanza di ResiliencePipelineBuilder, che viene utilizzata per aggiungere strategie di resilienza alla pipeline.

Considera il seguente esempio basato su stringhe:

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 ripetizione e timeout.

Ogni pipeline è configurata per un determinato key, e ciascun key viene utilizzato per identificare il suo corrispondente ResiliencePipeline quando si ottiene la pipeline dal fornitore. Il parametro di tipo generico del metodo key è AddResiliencePipeline.

Estensioni del costruttore di pipeline di resilienza

Per aggiungere una strategia alla pipeline, chiama uno dei metodi di estensione disponibili sull'istanza Add*.

  • AddRetry: Riprova se qualcosa fallisce, il che è utile quando il problema è temporaneo e potrebbe scomparire.
  • AddCircuitBreaker: Smetti di provare se qualcosa è rotto o occupato, il che ti avvantaggia evitando di perdere tempo e peggiorare le cose.
  • AddTimeout: Rinuncia se qualcosa richiede troppo tempo, il che può migliorare le prestazioni liberando risorse.
  • AddRateLimiter: Limita il numero di richieste che accetti, il che ti consente di controllare il carico di ingresso.
  • AddConcurrencyLimiter: Limita il numero di richieste che fai, consentendoti di controllare il carico in uscita.
  • AddFallback: Fai qualcos'altro quando si verificano problemi, il che migliora l'esperienza utente.
  • AddHedging: Emetti più richieste in caso di elevata latenza o guasto, il che può migliorare la reattività.

Per ulteriori informazioni, fare riferimento a Resilience strategies. Per esempi, vedere Build resilient HTTP apps: Key development patterns.

Arricchimento delle metriche

Arricchimento è l'augmentazione automatica della telemetria con uno stato ben noto, sotto forma di coppie nome/valore. Ad esempio, un'app potrebbe emettere un registro che include l'operazione e il codice risultato come colonne per rappresentare l'esito di qualche operazione. In questa situazione e a seconda del contesto periferico, l'arricchimento aggiunge Nome del Cluster, Nome del Processo, Regione, ID del Tenente e altro al registro mentre viene inviato al backend di telemetria. Quando viene aggiunto l'arricchimento, il codice dell'app non deve eseguire alcuna operazione aggiuntiva per trarre vantaggio dalle metriche arricchite.

Come funziona l'arricchimento

Si immaginino 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 registri metrici contengano le informazioni necessarie per individuare i guasti nei sistemi distribuiti. Senza arricchimento, l'onere ricade sul codice dell'applicazione di gestire internamente questo stato, integrarlo nel processo di registrazione e trasmetterlo manualmente. L'arricchimento semplifica questo processo, gestendolo senza soluzione di continuità senza influenzare la logica dell'app.

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

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

Sotto il cofano, l'arricchimento della resilienza è costruito sopra la Telemetria di Polly MeteringEnricher. Per ulteriori informazioni, vedere Polly: Metering enrichment.

Aggiungere l'arricchimento con resilienza

Oltre a registrare un pipeline di resilienza, puoi anche registrare un arricchimento di resilienza. Per aggiungere un arricchimento, chiama il metodo di estensioni AddResilienceEnricher(IServiceCollection) sull'istanza IServiceCollection.

services.AddResilienceEnricher();

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

Utilizzare 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 è 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:

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

Eseguire la pipeline di resilienza

Per utilizzare il resilience pipeline, chiama uno qualsiasi dei metodi disponibili sull'istanza Execute*. Si consideri ad esempio una chiamata al ExecuteAsync metodo :

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

    return ValueTask.CompletedTask;
});

Il codice precedente esegue il delegate nel metodo ExecuteAsync. Quando si verificano dei guasti, 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 di ripetizione) prima che l'errore venga propagato.

Passaggi successivi