Condividi tramite


Libreria HybridCache in ASP.NET Core

Importante

HybridCache è attualmente ancora in anteprima, ma verrà completamente rilasciato dopo .NET 9.0 in una versione secondaria futura delle estensioni .NET.

Questo articolo illustra come configurare e usare la HybridCache libreria in un'app ASP.NET Core. Per un'introduzione alla libreria, vedere la HybridCache sezione della panoramica della memorizzazione nella cache.

Ottenere la libreria

Installare il pacchetto Microsoft.Extensions.Caching.Hybrid.

dotnet add package Microsoft.Extensions.Caching.Hybrid --version "9.0.0-preview.7.24406.2"

Registrare il servizio

Aggiungere il HybridCache servizio al contenitore di inserimento delle dipendenze chiamando AddHybridCache:

// Add services to the container.
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddAuthorization();

builder.Services.AddHybridCache();

Il codice precedente registra il HybridCache servizio con le opzioni predefinite. L'API di registrazione può anche configurare opzioni e serializzazione.

Ottenere e archiviare le voci della cache

Il HybridCache servizio fornisce un GetOrCreateAsync metodo con due overload, prendendo una chiave e:

  • Metodo factory.
  • Stato e metodo factory.

Il metodo usa la chiave per tentare di recuperare l'oggetto dalla cache primaria. Se l'elemento non viene trovato nella cache primaria (un mancato riscontro nella cache), controlla la cache secondaria se ne è configurata una. Se non trova i dati (un altro mancato riscontro nella cache), chiama il metodo factory per ottenere l'oggetto dall'origine dati. Archivia quindi l'oggetto nelle cache primarie e secondarie. Il metodo factory non viene mai chiamato se l'oggetto viene trovato nella cache primaria o secondaria (un riscontro nella cache).

Il HybridCache servizio garantisce che un solo chiamante simultaneo per una determinata chiave chiami il metodo factory e tutti gli altri chiamanti attendino il risultato di tale chiamata. Oggetto CancellationToken passato a GetOrCreateAsync rappresenta l'annullamento combinato di tutti i chiamanti simultanei.

Overload principale GetOrCreateAsync

L'overload senza stato di GetOrCreateAsync è consigliato per la maggior parte degli scenari. Il codice da chiamare è relativamente semplice. Ecco un esempio:

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
            cancellationToken: token
        );
    }

    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

Linee guida per la chiave della cache

Il key passato a GetOrCreateAsync deve identificare in modo univoco i dati memorizzati nella cache:

  • In termini di valori di identificatore usati per recuperare i dati dall'origine.
  • In termini di altri dati memorizzati nella cache nell'applicazione.

Entrambi i tipi di univocità vengono in genere assicurati usando la concatenazione di stringhe per creare una singola stringa chiave composta da parti diverse concatenate in una sola stringa. Ad esempio:

cache.GetOrCreateAsync($"/orders/{region}/{orderId}", ...);

o

cache.GetOrCreateAsync($"user_prefs_{userId}", ...);

È responsabilità del chiamante assicurarsi che uno schema chiave sia valido e non possa causare confusione nei dati.

È consigliabile non usare l'input dell'utente esterno nella chiave della cache. Ad esempio, non usare valori di string non elaborati da un'interfaccia utente come parte di una chiave della cache. Tali chiavi potrebbero consentire tentativi di accesso dannoso o potrebbero essere usati in un attacco Denial of Service saturando la cache con chiavi senza significato generate da stringhe casuali. Negli esempi validi precedenti, i dati dell'ordine e i dati della preferenza utente sono chiaramente distinti.

  • orderid e userId sono identificatori generati internamente.
  • region potrebbe essere un'enumerazione o una stringa da un elenco predefinito di aree note.

Non esiste alcun significato per i token, ad esempio / o _. L'intero valore della chiave viene considerato come una stringa di identificazione opaca. In questo caso, è possibile omettere il / e _ senza alcuna modifica al modo in cui le funzioni della cache, ma un delimitatore viene in genere usato per evitare ambiguità, ad esempio $"order{customerId}{orderId}" potrebbe causare confusione tra:

  • customerId 42 con orderId 123
  • customerId 421 con orderId 23

(entrambi generano la chiave della cache order42123)

Queste linee guida si applicano allo stesso modo a qualsiasi API cache basata su string, ad esempio HybridCache, IDistributedCachee IMemoryCache.

Si noti che la sintassi di stringa interpolata inline ($"..." negli esempi precedenti di chiavi valide) si trova direttamente all'interno della chiamata GetOrCreateAsync. Questa sintassi è consigliata quando si usa HybridCache, perché consente miglioramenti futuri pianificati che ignorano la necessità di allocare un string per la chiave in molti scenari.

Considerazioni chiave aggiuntive

  • Le chiavi possono essere limitate a lunghezze massime valide. Ad esempio, l'implementazione HybridCache predefinita (tramite AddHybridCache(...)) limita le chiavi a 1024 caratteri per impostazione predefinita. Tale numero è configurabile tramite HybridCacheOptions.MaximumKeyLength, con chiavi più lunghe ignorando i meccanismi della cache per evitare la saturazione.
  • Le chiavi devono essere sequenze Unicode valide. Se vengono passate sequenze Unicode non valide, il comportamento non è definito.
  • Quando si usa una cache secondaria out-of-process, ad esempio IDistributedCache, l'implementazione back-end specifica può imporre restrizioni aggiuntive. Come esempio ipotetico, un particolare back-end potrebbe utilizzare una logica delle chiavi insensibile alle maiuscole. Il HybridCache predefinito (tramite AddHybridCache(...)) rileva questo scenario per evitare attacchi di confusione, ma può comunque comportare la sovrascrittura o la rimozione delle chiavi in conflitto prima del previsto.

Overload alternativo GetOrCreateAsync

L'overload alternativo potrebbe ridurre il sovraccarico delle variabili acquisite e dei callback per istanza, ma a scapito di codice più complesso. Per la maggior parte degli scenari, l'aumento delle prestazioni non supera la complessità del codice. Ecco un esempio che usa l'overload alternativo:

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            (name, id, obj: this),
            static async (state, token) =>
            await state.obj.GetDataFromTheSourceAsync(state.name, state.id, token),
            cancellationToken: token
        );
    }

    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

Metodo SetAsync

In molti scenari, GetOrCreateAsync è l'unica API necessaria. Ma HybridCache deve SetAsync anche archiviare un oggetto nella cache senza tentare di recuperarlo per primo.

Rimuovere le voci della cache per chiave

Quando i dati sottostanti per una voce della cache vengono modificati prima della scadenza, rimuovere la voce in modo esplicito chiamando RemoveAsync con la chiave alla voce. Un overload consente di specificare una raccolta di valori chiave.

Quando una voce viene rimossa, viene rimossa dalle cache primarie e secondarie.

Rimuovere le voci della cache in base al tag

Importante

Questa funzionalità è ancora in fase di sviluppo. Se provi a rimuovere le voci per tag, noterai che non ha effetto.

I tag possono essere usati per raggruppare le voci della cache e invalidarle insieme.

Impostare i tag quando si chiama GetOrCreateAsync, come illustrato nell'esempio seguente:

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        var tags = new List<string> { "tag1", "tag2", "tag3" };
        var entryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(1),
            LocalCacheExpiration = TimeSpan.FromMinutes(1)
        };
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
            entryOptions,
            tags,
            cancellationToken: token
        );
    }
    
    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

Rimuovere tutte le voci per un tag specificato chiamando RemoveByTagAsync con il valore del tag. Un overload consente di specificare una raccolta di valori di tag.

Quando una voce viene rimossa, viene rimossa dalle cache primarie e secondarie.

Opzioni

Il AddHybridCache metodo può essere usato per configurare le impostazioni predefinite globali. L'esempio seguente illustra come configurare alcune delle opzioni disponibili:

// Add services to the container.
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();

builder.Services.AddHybridCache(options =>
    {
        options.MaximumPayloadBytes = 1024 * 1024;
        options.MaximumKeyLength = 1024;
        options.DefaultEntryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(5),
            LocalCacheExpiration = TimeSpan.FromMinutes(5)
        };
    });

Il GetOrCreateAsync metodo può anche accettare un HybridCacheEntryOptions oggetto per eseguire l'override delle impostazioni predefinite globali per una voce di cache specifica. Ecco un esempio:

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        var tags = new List<string> { "tag1", "tag2", "tag3" };
        var entryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(1),
            LocalCacheExpiration = TimeSpan.FromMinutes(1)
        };
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
            entryOptions,
            tags,
            cancellationToken: token
        );
    }
    
    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

Per altre informazioni sulle opzioni, vedere il codice sorgente:

Limiti

Le proprietà seguenti di consentono di configurare i limiti applicabili a tutte le voci della HybridCacheOptions cache:

  • MaximumPayloadBytes: dimensione massima di una voce della cache. Il valore predefinito è 1 MB. I tentativi di archiviare i valori di questa dimensione vengono registrati e il valore non viene archiviato nella cache.
  • MaximumKeyLength: lunghezza massima di una chiave della cache. Il valore predefinito è 1024 caratteri. I tentativi di archiviare i valori di questa dimensione vengono registrati e il valore non viene archiviato nella cache.

Serializzazione

L'uso di una cache secondaria out-of-process richiede la serializzazione. La serializzazione viene configurata come parte della registrazione del HybridCache servizio. I serializzatori specifici del tipo e per utilizzo generico possono essere configurati tramite i AddSerializer metodi e AddSerializerFactory , concatenati dalla AddHybridCache chiamata. Per impostazione predefinita, la libreria gestisce string e byte[] internamente e usa System.Text.Json per tutto il resto. HybridCache può anche usare altri serializzatori, ad esempio protobuf o XML.

L'esempio seguente configura il servizio per l'uso di un serializzatore protobuf specifico del tipo:

// Add services to the container.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();

builder.Services.AddHybridCache(options =>
    {
        options.DefaultEntryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromSeconds(10),
            LocalCacheExpiration = TimeSpan.FromSeconds(5)
        };
    }).AddSerializer<SomeProtobufMessage, 
        GoogleProtobufSerializer<SomeProtobufMessage>>();

L'esempio seguente configura il servizio per l'uso di un serializzatore protobuf per utilizzo generico in grado di gestire molti tipi protobuf:

// Add services to the container.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();

builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromSeconds(10),
        LocalCacheExpiration = TimeSpan.FromSeconds(5)
    };
}).AddSerializerFactory<GoogleProtobufSerializerFactory>();

La cache secondaria richiede un archivio dati, ad esempio Redis o SqlServer. Per usare cache di Azure per Redis, ad esempio:

  • Installare il pacchetto Microsoft.Extensions.Caching.StackExchangeRedis.

  • Creare un'istanza di cache di Azure per Redis.

  • Ottenere un stringa di connessione che si connette all'istanza di Redis. Trovare il stringa di connessione selezionando Mostra chiavi di accesso nella pagina Panoramica del portale di Azure.

  • Archiviare il stringa di connessione nella configurazione dell'app. Ad esempio, usare un file di segreti utente simile al codice JSON seguente, con il stringa di connessione nella ConnectionStrings sezione . Sostituire <the connection string> con il stringa di connessione effettivo:

    {
      "ConnectionStrings": {
        "RedisConnectionString": "<the connection string>"
      }
    }
    
  • Registrare nell'implementazione di inserimento delle dipendenze IDistributedCache fornita dal pacchetto Redis. A tale scopo, chiamare AddStackExchangeRedisCachee passare il stringa di connessione. Ad esempio:

    builder.Services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = 
            builder.Configuration.GetConnectionString("RedisConnectionString");
    });
    
  • L'implementazione di Redis IDistributedCache è ora disponibile dal contenitore di inserimento delle dipendenze dell'app. HybridCache lo usa come cache secondaria e usa il serializzatore configurato per tale cache.

Per altre informazioni, vedere l'app di esempio di serializzazione HybridCache.

Archiviazione cache

Per impostazione predefinita HybridCache , MemoryCache usa per l'archiviazione della cache primaria. Le voci della cache vengono archiviate in-process, quindi ogni server dispone di una cache separata che viene persa ogni volta che viene riavviato il processo del server. Per l'archiviazione out-of-process secondaria, ad esempio Redis o SQL Server, HybridCache usa IDistributedCache configurata, se presente. Ma anche senza un'implementazione IDistributedCache, il servizio HybridCache fornisce comunque la memorizzazione nella cache in-process e protezione contro il caos .

Nota

Quando si invalidano le voci della cache in base alla chiave o ai tag, vengono invalidate nel server corrente e nell'archiviazione out-of-process secondaria. Tuttavia, la cache in memoria in altri server non è interessata.

Ottimizzare le prestazioni

Per ottimizzare le prestazioni, configurare per riutilizzare HybridCache gli oggetti ed evitare byte[] allocazioni.

Riutilizzare gli oggetti

Riutilizzando istanze, HybridCache è possibile ridurre il sovraccarico delle allocazioni di CPU e oggetti associate alla deserializzazione per chiamata. Ciò può comportare miglioramenti delle prestazioni negli scenari in cui gli oggetti memorizzati nella cache sono di grandi dimensioni o a cui si accede di frequente.

Nel codice esistente tipico che usa IDistributedCache, ogni recupero di un oggetto dalla cache comporta la deserializzazione. Questo comportamento significa che ogni chiamante simultaneo ottiene un'istanza separata dell'oggetto, che non può interagire con altre istanze. Il risultato è thread safety, poiché non esiste alcun rischio di modifiche simultanee alla stessa istanza dell'oggetto.

Poiché gran parte HybridCache dell'utilizzo verrà adattato dal codice esistente IDistributedCache , HybridCache mantiene questo comportamento per impostazione predefinita per evitare di introdurre bug di concorrenza. Tuttavia, gli oggetti sono intrinsecamente thread-safe se:

  • Sono tipi non modificabili.
  • Il codice non li modifica.

In questi casi, informare HybridCache che è sicuro riutilizzare le istanze in base a:

  • Contrassegnare il tipo come sealed. La sealed parola chiave in C# indica che la classe non può essere ereditata.
  • Applicazione dell'attributo [ImmutableObject(true)] al tipo. L'attributo [ImmutableObject(true)] indica che lo stato dell'oggetto non può essere modificato dopo la creazione.

Evitare byte[] allocazioni

HybridCache fornisce anche API facoltative per IDistributedCache le implementazioni, per evitare byte[] allocazioni. Questa funzionalità viene implementata dalle versioni di anteprima dei Microsoft.Extensions.Caching.StackExchangeRedis pacchetti e Microsoft.Extensions.Caching.SqlServer . Per altre informazioni, vedere IBufferDistributedCache Ecco i comandi dell'interfaccia della riga di comando di .NET per installare i pacchetti:

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
dotnet add package Microsoft.Extensions.Caching.SqlServer

Implementazioni di HybridCache personalizzate

Un'implementazione concreta della HybridCache classe astratta è inclusa nel framework condiviso e viene fornita tramite inserimento delle dipendenze. Gli sviluppatori sono tuttavia invitati a fornire implementazioni personalizzate dell'API.

Compatibilità

La HybridCache libreria supporta runtime .NET meno recenti, fino a .NET Framework 4.7.2 e .NET Standard 2.0.

Risorse aggiuntive

Per altre informazioni su HybridCache, vedere le risorse seguenti: