Dela via


HybridCache-bibliotek i ASP.NET Core

Den här artikeln beskriver hur du konfigurerar och använder HybridCache-biblioteket i en ASP.NET Core-app. En introduktion till biblioteket finns i avsnittet HybridCache i översikten över cachelagring.

Hämta biblioteket

Installera Microsoft.Extensions.Caching.Hybrid-paketet.

dotnet add package Microsoft.Extensions.Caching.Hybrid

Registrera tjänsten

Lägg till HybridCache-tjänsten i beroendeinjektionscontainern (DI) genom att anropa AddHybridCache:

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

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

builder.Services.AddHybridCache();

Föregående kod registrerar HybridCache-tjänsten med standardalternativ. Registrerings-API:et kan också konfigurera alternativ och serialisering.

Hämta och lagra cache-objekt

Tjänsten HybridCache tillhandahåller en GetOrCreateAsync-metod med två överlagringar, där en tar en nyckel och:

  • En fabriksmetod.
  • Tillstånd och en fabriksmetod.

Metoden använder nyckeln för att försöka hämta objektet från den primära cachen. Om objektet inte hittas i den primära cachen (en cachemiss) kontrollerar det sedan den sekundära cachen om en har konfigurerats. Om den inte hittar data där (en annan cachemiss) anropas fabriksmetoden för att hämta objektet från datakällan. Objektet lagras sedan i både primära och sekundära cacheminnen. Fabriksmetoden anropas aldrig om objektet hittas i den primära eller sekundära cachen (en cacheträff).

Tjänsten HybridCache ser till att endast en samtidig anropare för en viss nyckel anropar fabriksmetoden och alla andra anropare väntar på resultatet av det anropet. Den CancellationToken som skickas till GetOrCreateAsync representerar den kombinerade annulleringen av alla samtidiga anropare.

Den huvudsakliga GetOrCreateAsync-överbelastningen

Tillståndslös överbelastning av GetOrCreateAsync rekommenderas i de flesta scenarier. Koden som ska anropas är relativt enkel. Här är ett exempel:

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;
    }
}

Vägledning för cachenyckel

Den key som skickas till GetOrCreateAsync måste unikt identifiera de data som cachelagras:

  • När det gäller de identifierarvärden som används för att hämta dessa data från källan.
  • När det gäller andra data som cachelagras i programmet.

Båda typerna av unikhet säkerställs vanligtvis med hjälp av strängsammanfogning för att göra en enda nyckelsträng bestående av olika delar sammanfogade i en sträng. Till exempel:

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

Eller

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

Det är anroparens ansvar att se till att ett nyckelschema är giltigt och inte kan orsaka att data blir förvirrade.

Undvik att använda extern användarinmatning direkt i cachenycklar. Använd till exempel inte råsträngar från användargränssnitt som cachenycklar. Detta kan utsätta din app för säkerhetsrisker, till exempel obehörig åtkomst eller överbelastningsattacker som orsakas av att cacheminnet översvämmas med slumpmässiga eller meningslösa nycklar. I de föregående giltiga exemplen är order - och användarinställningsdata tydligt avgränsade och använder betrodda identifierare:

  • orderid och userId är internt genererade identifierare.
  • region kan vara en uppräkning eller sträng från en fördefinierad lista över kända regioner.

Ingen signifikans placeras på token som / eller _. Hela nyckelvärdet behandlas som en ogenomskinlig identifieringssträng. I det här fallet kan du utelämna / och _ utan att ändra hur cachen fungerar, men en avgränsare används vanligtvis för att undvika tvetydighet , till exempel $"order{customerId}{orderId}" kan orsaka förvirring mellan:

  • customerId 42 med orderId 123
  • customerId 421 med orderId 23

Båda föregående exempel skulle generera cachenyckeln order42123.

Den här vägledningen gäller även för alla string-baserade cache-API:et, till exempel HybridCache, IDistributedCacheoch IMemoryCache.

Observera att den infogade interpolerade strängsyntaxen ($"..." i föregående exempel på giltiga nycklar) finns direkt i GetOrCreateAsync-anropet. Den här syntaxen rekommenderas när du använder HybridCacheeftersom den möjliggör planerade framtida förbättringar som kringgår behovet av att allokera en string för nyckeln i många scenarier.

Ytterligare viktiga överväganden

  • Nycklar kan begränsas till giltiga maximala längder. Standardimplementeringen för HybridCache (via AddHybridCache(...)) begränsar till exempel nycklar till 1 024 tecken som standard. Det talet kan konfigureras via HybridCacheOptions.MaximumKeyLength, med längre nycklar som kringgår cachemekanismerna för att förhindra mättnad.
  • Nycklarna måste vara giltiga Unicode-sekvenser. Om ogiltiga Unicode-sekvenser skickas är beteendet odefinierat.
  • När du använder en sekundär cache som inte är bearbetad, till exempel IDistributedCache, kan serverdelsimplementeringen införa ytterligare begränsningar. Som ett hypotetiskt exempel kan en viss backend använda skiftlägesokänslig nyckellogik. Standardvärdet HybridCache (via AddHybridCache(...)) identifierar det här scenariot för att förhindra förvirringsattacker eller aliasattacker (med bitvis strängjämlikhet). Det här scenariot kan dock fortfarande leda till att motstridiga nycklar skrivs över eller tas bort tidigare än förväntat.

Alternativ GetOrCreateAsync överbelastning

Den alternativa överlastningen kan minska en del av överbelastningen från insamlade variabler och återanrop vid vissa instanser, men på bekostnad av mer komplex kod. I de flesta scenarier uppväger prestandaökningen inte kodkomplexiteten. Här är ett exempel som använder alternativ överlagring:

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;
    }
}

Metoden SetAsync

I många scenarier är GetOrCreateAsync det enda API som behövs. Men HybridCache har också SetAsync för att lagra ett objekt i cacheminnet utan att försöka hämta det först.

Ta bort cacheposter med hjälp av nyckel

När underliggande data för en cachepost ändras innan den upphör att gälla tar du bort posten explicit genom att anropa RemoveAsync med nyckeln till posten. Med en överbelastning kan du ange en samling nyckelvärden.

När en post tas bort tas den bort från både de primära och sekundära cacheminnena.

Ta bort cacheposter efter tagg

Taggar kan användas för att gruppera cacheposter och ogiltigförklara dem tillsammans.

Ange taggar när du anropar GetOrCreateAsync, enligt följande exempel:

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;
    }
}

Ta bort alla poster för en angiven tagg genom att anropa RemoveByTagAsync med värdet för taggen. Med en överbelastning kan du ange en samling av taggvärden.

Varken IMemoryCache eller IDistributedCache har direkt stöd för begreppet taggar, så taggbaserad ogiltighet är endast en logisk åtgärd. Det tar inte aktivt bort värden från antingen lokal eller distribuerad cache. I stället ser det till att när du tar emot data med sådana taggar behandlas data som en cachemiss från både den lokala cachen och fjärrcachen. Värdena upphör att gälla från IMemoryCache och IDistributedCache på vanligt sätt baserat på den konfigurerade livslängden.

Ta bort alla cacheposter

Taggen asterisk (*) är reserverad som ett jokertecken och tillåts inte mot enskilda värden. Anrop RemoveByTagAsync("*") innebär att allaHybridCache data ogiltigförklaras, även data som inte har några taggar. Precis som med enskilda taggar är detta en logisk åtgärd och enskilda värden fortsätter att finnas tills de upphör att gälla naturligt. Matchningar i Glob-stil stöds inte. Du kan till exempel inte använda RemoveByTagAsync("foo*") för att ta bort allt som börjar med foo.

Ytterligare taggöverväganden

  • Systemet begränsar inte antalet taggar som du kan använda, men stora uppsättningar taggar kan påverka prestanda negativt.
  • Taggar får inte vara tomma, endast bestå av blanksteg, eller det reserverade värdet *.

Alternativ

Metoden AddHybridCache kan användas för att konfigurera globala standardvärden. I följande exempel visas hur du konfigurerar några av de tillgängliga alternativen:

// 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)
        };
    });

Metoden GetOrCreateAsync kan också ta ett HybridCacheEntryOptions objekt för att åsidosätta de globala standardvärdena för en specifik cachepost. Här är ett exempel:

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;
    }
}

Mer information om alternativen finns i källkoden:

Gränser

Med följande egenskaper för HybridCacheOptions kan du konfigurera gränser som gäller för alla cacheposter:

  • MaximumPayloadBytes – Maximal storlek på en cachepost. Standardvärdet är 1 MB. Försök att lagra värden över den här storleken loggas och värdet lagras inte i cacheminnet.
  • MaximumKeyLength – Maximal längd på en cachenyckel. Standardvärdet är 1 024 tecken. Försök att lagra värden över den här storleken loggas och värdet lagras inte i cacheminnet.

Serialisering

Användning av ett sekundärt cacheminne utan process kräver serialisering. Serialisering konfigureras som en del av registreringen av HybridCache-tjänsten. Typspecifika och allmänna serialiserare kan konfigureras via metoderna AddSerializer och AddSerializerFactory, länkade från AddHybridCache-anropet. Som standard hanterar biblioteket string och byte[] internt och använder System.Text.Json för allt annat. HybridCache kan också använda andra serialiserare, till exempel protobuf eller XML.

I följande exempel konfigureras tjänsten för att använda en typspecifik protobuf-serialiserare:

// 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>>();

I följande exempel konfigureras tjänsten för att använda en protobuf-serialiserare för generell användning som kan hantera många protobuf-typer:

// 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>();

Den sekundära cachen kräver ett datalager, till exempel Redis, SQL Server eller Postgres. Så här använder du Azure Cache for Redis, till exempel:

  • Installera Microsoft.Extensions.Caching.StackExchangeRedis-paketet.

  • Skapa en instans av Azure Cache for Redis.

  • Hämta en anslutningssträng som ansluter till Redis-instansen. Leta upp anslutningssträngen genom att välja Visa åtkomstnycklar på sidan Översikt i Azure-portalen.

  • Lagra anslutningssträngen i appens konfiguration. Använd till exempel en fil med användarhemligheter som ser ut som följande JSON, med anslutningssträngen i avsnittet ConnectionStrings. Ersätt <the connection string> med den faktiska anslutningssträngen:

    {
      "ConnectionStrings": {
        "RedisConnectionString": "<the connection string>"
      }
    }
    
  • Registrera i DI den IDistributedCache implementering som Redis-paketet tillhandahåller. Det gör du genom att anropa AddStackExchangeRedisCacheoch skicka anslutningssträngen. Till exempel:

    builder.Services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = 
            builder.Configuration.GetConnectionString("RedisConnectionString");
    });
    
  • Redis-IDistributedCache-implementeringen är nu tillgänglig från appens DI-container. HybridCache använder den som sekundär cache och använder serialiseraren som konfigurerats för den.

Mer information finns i appen HybridCache serialiseringsexempel.

Cachelagring

Som standard använder HybridCacheMemoryCache för sin primära cachelagring. Cacheposter lagras i processen, så varje server har en separat cache som går förlorad när serverprocessen startas om. För sekundär out-of-process-lagring, till exempel Redis, SQL Server eller Postgres, HybridCache använder den konfigurerade IDistributedCache implementeringen, om sådan finns. Men även utan en IDistributedCacheimplementering tillhandahåller HybridCache-tjänsten fortfarande processbaserad cachelagring och stampede-skydd.

Anteckning

När cacheposter inaktiveras efter nycklar eller taggar, inaktiveras de på den aktuella servern och i den sekundära lagringen som opererar utanför processen. Minnesintern cache på andra servrar påverkas dock inte.

Optimera prestanda

Du kan optimera prestanda genom att konfigurera HybridCache att återanvända objekt och undvika byte[] allokeringar.

Återanvända objekt

Genom att återanvända instanser kan HybridCache minska kostnaderna för PROCESSOR- och objektallokeringar som är associerade med deserialisering per anrop. Detta kan leda till prestandaförbättringar i scenarier där cachelagrade objekt är stora eller används ofta.

I vanlig befintlig kod som använder IDistributedCacheresulterar varje hämtning av ett objekt från cachen i deserialisering. Det här beteendet innebär att varje samtidig anropare får en separat instans av objektet, som inte kan interagera med andra instanser. Resultatet är trådsäkerhet eftersom det inte finns någon risk för samtidiga ändringar av samma objektinstans.

Eftersom mycket HybridCache användning kommer att anpassas från befintlig IDistributedCache kod, bevarar HybridCache det här beteendet som standard för att undvika samtidighetsbuggar. Objekt är dock i sig trådsäkra om:

  • De är oföränderliga typer.
  • Koden ändrar dem inte.

I sådana fall bör du informera om HybridCache att det är säkert att återanvända instanser genom att göra båda följande ändringar:

  • Markera typen som sealed. Nyckelordet sealed i C# innebär att klassen inte kan ärvas.
  • Tillämpa attributet [ImmutableObject(true)] på typen. Attributet [ImmutableObject(true)] anger att objektets tillstånd inte kan ändras när det har skapats.

Undvik byte[] allokeringar

HybridCache tillhandahåller även valfria API:er för IDistributedCache implementeringar för att undvika byte[] allokeringar. Den här funktionen implementeras av förhandsversionerna av paketen Microsoft.Extensions.Caching.StackExchangeRedis, Microsoft.Extensions.Caching.SqlServeroch Microsoft.Extensions.Caching.Postgres . Mer information finns i IBufferDistributedCache. Här är .NET CLI-kommandona för att installera paketen:

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

Anpassade HybridCache-implementeringar

En konkret implementering av den HybridCache abstrakta klassen ingår i det delade ramverket och tillhandahålls via beroendeinmatning. Men utvecklare är välkomna att tillhandahålla eller använda anpassade implementeringar av API:et, till exempel FusionCache.

Använd Hybrid Cache med inbyggd AOT

Följande interna AOT-specifika överväganden gäller för HybridCache:

  • serialisering

    Nativ AOT stöder inte runtime-reflektionsbaserad serialisering. Om du cachelagrar anpassade typer måste du använda källgeneratorer eller uttryckligen konfigurera serialiserare som är kompatibla med AOT, till exempel System.Text.Json källgenerering. HybridCache är fortfarande under utveckling, och att förenkla sättet att använda det med AOT är en hög prioritet för denna utveckling. Mer information finns i pull-begäran dotnet/extensions#6475

  • Trimning

    Kontrollera att alla typer som du cachelagrar refereras till på ett sätt som förhindrar att de trimmas av AOT-kompilatorn. Att använda källgeneratorer för serialisering hjälper till med det här kravet. Mer information finns i ASP.NET Core-stöd för Native AOT.

Om du konfigurerar serialisering och trimning korrekt, fungerar HybridCache på samma sätt i Native AOT som i vanliga ASP.NET Core-appar.

Kompatibilitet

HybridCache-biblioteket stöder äldre .NET-körningar, ned till .NET Framework 4.7.2 och .NET Standard 2.0.

Ytterligare resurser

Mer information finns i källkoden HybridCache