Sdílet prostřednictvím


Ukládání do mezipaměti v .NET

V tomto článku se dozvíte o různých mechanismech ukládání do mezipaměti. Ukládání dat do mezipaměti je ukládání dat v přechodné vrstvě, což zrychlová následné načítání dat. Konceptuálně je ukládání do mezipaměti strategií optimalizace výkonu a úvahou o návrhu. Ukládání do mezipaměti může výrazně zlepšit výkon aplikace tím, že data, která se mění jen zřídka nebo je nákladné je získat, zpřístupňuje snáze. Tento článek představuje tři přístupy k ukládání do mezipaměti a poskytuje vzorový zdrojový kód pro každý z nich:

Důležité

V rámci .NET existují dvě MemoryCache třídy, jedna v System.Runtime.Caching oboru názvů a druhá v Microsoft.Extensions.Caching oboru názvů:

I když se tento článek zaměřuje na ukládání do mezipaměti, neobsahuje System.Runtime.Caching balíček NuGet. Všechny odkazy na MemoryCache jsou v oboru názvů Microsoft.Extensions.Caching.

Microsoft.Extensions.* Všechny balíčky jsou připravené na injektáž závislostí (DI). Rozhraní IMemoryCache, HybridCache a IDistributedCache lze použít jako služby.

Paměťová cache

V této části se dozvíte o balíčku Microsoft.Extensions.Caching.Memory . Aktuální implementace IMemoryCache je obálka zajišťující rozhraní API s bohatými funkcemi kolem ConcurrentDictionary<TKey,TValue>. Položky v mezipaměti jsou reprezentovány ICacheEntry a mohou být libovolné object. Řešení mezipaměti v paměti je skvělé pro aplikace, které běží na jednom serveru, kde data uložená v mezipaměti pronajímají paměť v procesu aplikace.

Návod

U scénáře ukládání do mezipaměti s více servery zvažte použití přístupu distribuovaného ukládání do mezipaměti jako alternativu ke ukládání do mezipaměti v paměti.

Rozhraní API pro ukládání do mezipaměti v paměti

Uživatel mezipaměti má kontrolu nad posuvnou i absolutní dobou platnosti.

Nastavení vypršení platnosti způsobí, že se položky v mezipaměti vyřadí , pokud nebudou v době vypršení platnosti přístupné. Uživatelé mají další možnosti pro řízení položek mezipaměti prostřednictvím MemoryCacheEntryOptions. Každý ICacheEntry je spárován s MemoryCacheEntryOptions, který poskytuje funkce pro vyřazení při vypršení pomocí IChangeToken, nastavení priority pomocí CacheItemPriority a ovládání pomocí ICacheEntry.Size. Relevantní metody rozšíření jsou:

Příklad mezipaměti v paměti

Chcete-li použít výchozí IMemoryCache implementaci, zavolejte metodu AddMemoryCache rozšíření pro registraci všech požadovaných služeb v DI. V následující ukázce kódu se obecný hostitel používá ke zveřejnění funkcí DI:

using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
using IHost host = builder.Build();

V závislosti na vaší úloze .NET můžete přistupovat k IMemoryCache různými způsoby, například pomocí injektování do konstruktoru. V této ukázce použijete instanci IServiceProvider na host a zavoláte obecnou rozšiřující metodu GetRequiredService<T>(IServiceProvider).

IMemoryCache cache =
    host.Services.GetRequiredService<IMemoryCache>();

S zaregistrovanými službami ukládání do mezipaměti v paměti a vyřešenými prostřednictvím direktu můžete začít s ukládáním do mezipaměti. Tato ukázka prochází písmeny anglické abecedy od A do Z. Typ record AlphabetLetter obsahuje odkaz na písmeno a vygeneruje zprávu.

file record AlphabetLetter(char Letter)
{
    internal string Message =>
        $"The '{Letter}' character is the {Letter - 64} letter in the English alphabet.";
}

Návod

Modifikátor file přístupu se používá u AlphabetLetter typu, protože je definován v souboru Program.cs a je k němu přístup možný pouze z tohoto souboru. Další informace najdete v souboru (referenční dokumentace jazyka C#). Úplný zdrojový kód najdete v části Program.cs .

Ukázka obsahuje pomocnou funkci, která prochází písmeny abecedy:

static async ValueTask IterateAlphabetAsync(
    Func<char, Task> asyncFunc)
{
    for (char letter = 'A'; letter <= 'Z'; ++letter)
    {
        await asyncFunc(letter);
    }

    Console.WriteLine();
}

V předchozím kódu jazyka C#:

  • Očekává se Func<char, Task> asyncFunc při každé iteraci a předává aktuální letter.
  • Po zpracování všech písmen se do konzoly zapíše prázdný řádek.

Chcete-li přidat položky do mezipaměti, zavolejte jednu z API: Create nebo Set.

var addLettersToCacheTask = IterateAlphabetAsync(letter =>
{
    MemoryCacheEntryOptions options = new()
    {
        AbsoluteExpirationRelativeToNow =
            TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
    };

    _ = options.RegisterPostEvictionCallback(OnPostEviction);

    AlphabetLetter alphabetLetter =
        cache.Set(
            letter, new AlphabetLetter(letter), options);

    Console.WriteLine($"{alphabetLetter.Letter} was cached.");

    return Task.Delay(
        TimeSpan.FromMilliseconds(MillisecondsDelayAfterAdd));
});
await addLettersToCacheTask;

V předchozím kódu jazyka C#:

  • Proměnná addLettersToCacheTask deleguje na IterateAlphabetAsync a je očekávána.
  • Diskutuje se o Func<char, Task> asyncFunc lambda.
  • Instance MemoryCacheEntryOptions je vytvořena s absolutním vypršením platnosti vzhledem k aktuálnímu okamžiku.
  • Zpětné volání po vyřazení je registrováno.
  • Objekt AlphabetLetter je instancován a předán do Set společně s letter a options.
  • Písmeno se zapíše do konzoly jako uložené v mezipaměti.
  • Nakonec se vrátí Task.Delay.

Pro každé písmeno v abecedě se položka mezipaměti zapíše s vypršením platnosti a zpětným voláním po vyřazení.

Zpětné volání po vyřazení zapíše do konzoly podrobnosti o hodnotě, která byla vyřazena.

static void OnPostEviction(
    object key, object? letter, EvictionReason reason, object? state)
{
    if (letter is AlphabetLetter alphabetLetter)
    {
        Console.WriteLine($"{alphabetLetter.Letter} was evicted for {reason}.");
    }
};

Teď, když je mezipaměť naplněná, čeká se na další volání IterateAlphabetAsync, ale tentokrát zavoláte IMemoryCache.TryGetValue.

var readLettersFromCacheTask = IterateAlphabetAsync(letter =>
{
    if (cache.TryGetValue(letter, out object? value) &&
        value is AlphabetLetter alphabetLetter)
    {
        Console.WriteLine($"{letter} is still in cache. {alphabetLetter.Message}");
    }

    return Task.CompletedTask;
});
await readLettersFromCacheTask;

Pokud cache obsahuje klíč letter a value je instancí AlphabetLetter, pak se to zapíše do konzoly. letter Když klíč není v mezipaměti, byl vyřazen a poté bylo vyvoláno jeho zpětné volání po vyřazení.

Další metody rozšíření

IMemoryCache je dodáván s mnoha praktickými metodami rozšíření, včetně asynchronního GetOrCreateAsync.

Dejte to všechno dohromady

Celý zdrojový kód ukázkové aplikace je program nejvyšší úrovně a vyžaduje dva balíčky NuGet:

using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
using IHost host = builder.Build();

IMemoryCache cache =
    host.Services.GetRequiredService<IMemoryCache>();

const int MillisecondsDelayAfterAdd = 50;
const int MillisecondsAbsoluteExpiration = 750;

static void OnPostEviction(
    object key, object? letter, EvictionReason reason, object? state)
{
    if (letter is AlphabetLetter alphabetLetter)
    {
        Console.WriteLine($"{alphabetLetter.Letter} was evicted for {reason}.");
    }
};

static async ValueTask IterateAlphabetAsync(
    Func<char, Task> asyncFunc)
{
    for (char letter = 'A'; letter <= 'Z'; ++letter)
    {
        await asyncFunc(letter);
    }

    Console.WriteLine();
}

var addLettersToCacheTask = IterateAlphabetAsync(letter =>
{
    MemoryCacheEntryOptions options = new()
    {
        AbsoluteExpirationRelativeToNow =
            TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
    };

    _ = options.RegisterPostEvictionCallback(OnPostEviction);

    AlphabetLetter alphabetLetter =
        cache.Set(
            letter, new AlphabetLetter(letter), options);

    Console.WriteLine($"{alphabetLetter.Letter} was cached.");

    return Task.Delay(
        TimeSpan.FromMilliseconds(MillisecondsDelayAfterAdd));
});
await addLettersToCacheTask;

var readLettersFromCacheTask = IterateAlphabetAsync(letter =>
{
    if (cache.TryGetValue(letter, out object? value) &&
        value is AlphabetLetter alphabetLetter)
    {
        Console.WriteLine($"{letter} is still in cache. {alphabetLetter.Message}");
    }

    return Task.CompletedTask;
});
await readLettersFromCacheTask;

await host.RunAsync();

file record AlphabetLetter(char Letter)
{
    internal string Message =>
        $"The '{Letter}' character is the {Letter - 64} letter in the English alphabet.";
}

Můžete upravit hodnoty MillisecondsDelayAfterAdd a MillisecondsAbsoluteExpiration a sledovat změny chování ve vztahu k vypršení platnosti a vyřazení položek uložených v mezipaměti. Následuje ukázkový výstup spuštění tohoto kódu. (Vzhledem k nedeterministické povaze událostí .NET se může váš výstup lišit.)

A was cached.
B was cached.
C was cached.
D was cached.
E was cached.
F was cached.
G was cached.
H was cached.
I was cached.
J was cached.
K was cached.
L was cached.
M was cached.
N was cached.
O was cached.
P was cached.
Q was cached.
R was cached.
S was cached.
T was cached.
U was cached.
V was cached.
W was cached.
X was cached.
Y was cached.
Z was cached.

A was evicted for Expired.
C was evicted for Expired.
B was evicted for Expired.
E was evicted for Expired.
D was evicted for Expired.
F was evicted for Expired.
H was evicted for Expired.
K was evicted for Expired.
L was evicted for Expired.
J was evicted for Expired.
G was evicted for Expired.
M was evicted for Expired.
N was evicted for Expired.
I was evicted for Expired.
P was evicted for Expired.
R was evicted for Expired.
O was evicted for Expired.
Q was evicted for Expired.
S is still in cache. The 'S' character is the 19 letter in the English alphabet.
T is still in cache. The 'T' character is the 20 letter in the English alphabet.
U is still in cache. The 'U' character is the 21 letter in the English alphabet.
V is still in cache. The 'V' character is the 22 letter in the English alphabet.
W is still in cache. The 'W' character is the 23 letter in the English alphabet.
X is still in cache. The 'X' character is the 24 letter in the English alphabet.
Y is still in cache. The 'Y' character is the 25 letter in the English alphabet.
Z is still in cache. The 'Z' character is the 26 letter in the English alphabet.

Vzhledem k tomu, že je nastaveno absolutní vypršení platnosti (MemoryCacheEntryOptions.AbsoluteExpirationRelativeToNow), všechny položky uložené v mezipaměti se nakonec vyřadí.

Ukládání mezipaměti služby Worker

Jednou z běžných strategií ukládání dat do mezipaměti je aktualizace mezipaměti nezávisle na spotřebě datových služeb. Šablona Služby pracovního procesu je skvělým příkladem, protože BackgroundService běží nezávisle (nebo na pozadí) z jiného kódu aplikace. Když aplikace začne běžet a hostuje implementaci IHostedService, odpovídající implementace (v tomto případě BackgroundService neboli "pracovník") začne běžet ve stejném procesu. Tyto hostované služby jsou prostřednictvím metody rozšíření zaregistrované v DI jako singletony AddHostedService<THostedService>(IServiceCollection) . Ostatní služby je možné zaregistrovat v DI s libovolnou životností služeb.

Důležité

Životnost služeb je důležitá pro pochopení. Když voláte AddMemoryCache k registraci všech služeb ukládání do paměťové mezipaměti, jsou služby registrovány jako singletony.

Scénář fotoslužy

Představte si, že vyvíjíte fotoslužbě, která využívá rozhraní API třetích stran přístupné přes protokol HTTP. Tato data fotek se často nemění, ale je jich hodně. Každá fotografie je reprezentována jednoduchým record:

namespace CachingExamples.Memory;

public readonly record struct Photo(
    int AlbumId,
    int Id,
    string Title,
    string Url,
    string ThumbnailUrl);

V následujícím příkladu uvidíte, že se v DI registruje několik služeb. Každá služba má jednu zodpovědnost.

using CachingExamples.Memory;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddMemoryCache();
builder.Services.AddHttpClient<CacheWorker>();
builder.Services.AddHostedService<CacheWorker>();
builder.Services.AddScoped<PhotoService>();
builder.Services.AddSingleton(typeof(CacheSignal<>));

using IHost host = builder.Build();

await host.StartAsync();

V předchozím kódu jazyka C#:

Zodpovídá PhotoService za získání fotek, které odpovídají zadaným kritériím (nebo filter):

using Microsoft.Extensions.Caching.Memory;

namespace CachingExamples.Memory;

public sealed class PhotoService(
        IMemoryCache cache,
        CacheSignal<Photo> cacheSignal,
        ILogger<PhotoService> logger)
{
    public async IAsyncEnumerable<Photo> GetPhotosAsync(Func<Photo, bool>? filter = default)
    {
        try
        {
            await cacheSignal.WaitAsync();

            Photo[] photos =
                (await cache.GetOrCreateAsync(
                    "Photos", _ =>
                    {
                        logger.LogWarning("This should never happen!");

                        return Task.FromResult(Array.Empty<Photo>());
                    }))!;

            // If no filter is provided, use a pass-thru.
            filter ??= _ => true;

            foreach (Photo photo in photos)
            {
                if (!default(Photo).Equals(photo) && filter(photo))
                {
                    yield return photo;
                }
            }
        }
        finally
        {
            cacheSignal.Release();
        }
    }
}

V předchozím kódu jazyka C#:

  • Konstruktor vyžaduje IMemoryCache, CacheSignal<Photo> a ILogger.
  • Metoda GetPhotosAsync :
    • Func<Photo, bool> filter Definuje parametr a vrátí hodnotu IAsyncEnumerable<Photo>.
    • Volá a čeká na _cacheSignal.WaitAsync() uvolnění. Tím se zajistí, že mezipaměť bude naplněna dříve, než se k ní přistoupí.
    • Volá _cache.GetOrCreateAsync(), asynchronně získává všechny fotky v mezipaměti.
    • Argument factory zaznamená upozornění a vrátí prázdné pole fotek . K tomu by nikdy nemělo dojít.
    • Každá fotografie v mezipaměti je iterována, filtrována a materializována pomocí yield return.
    • Nakonec se signál mezipaměti resetuje.

Uživatelé této služby mohou volat metodu GetPhotosAsync a odpovídajícím způsobem zpracovávat fotografie. Není vyžadován HttpClient, protože mezipaměť obsahuje fotografie.

Asynchronní signál je založen na zapouzdřené SemaphoreSlim instanci v rámci omezeného singletonu obecného typu. Spoléhá CacheSignal<T> na instanci SemaphoreSlim:

namespace CachingExamples.Memory;

public sealed class CacheSignal<T>
{
    private readonly SemaphoreSlim _semaphore = new(1, 1);

    /// <summary>
    /// Exposes a <see cref="Task"/> that represents the asynchronous wait operation.
    /// When signaled (consumer calls <see cref="Release"/>), the 
    /// <see cref="Task.Status"/> is set as <see cref="TaskStatus.RanToCompletion"/>.
    /// </summary>
    public Task WaitAsync() => _semaphore.WaitAsync();

    /// <summary>
    /// Exposes the ability to signal the release of the <see cref="WaitAsync"/>'s operation.
    /// Callers who were waiting, will be able to continue.
    /// </summary>
    public void Release() => _semaphore.Release();
}

V předchozím kódu jazyka C# se vzor dekorátoru používá k zabalení instance objektu SemaphoreSlim. Vzhledem k tomu, že je CacheSignal<T> zaregistrován jako singleton, lze ho použít napříč všemi životnostmi služeb s jakýmkoli obecným typem – v tomto případě Photo. Zodpovídá za signalizaci počátečního osinutí mezipaměti.

Jedná se CacheWorker o podtřídu BackgroundService:

using System.Net.Http.Json;
using Microsoft.Extensions.Caching.Memory;

namespace CachingExamples.Memory;

public sealed class CacheWorker(
    ILogger<CacheWorker> logger,
    HttpClient httpClient,
    CacheSignal<Photo> cacheSignal,
    IMemoryCache cache) : BackgroundService
{
    private readonly TimeSpan _updateInterval = TimeSpan.FromHours(3);

    private bool _isCacheInitialized = false;

    private const string Url = "https://jsonplaceholder.typicode.com/photos";

    public override async Task StartAsync(CancellationToken cancellationToken)
    {
        await cacheSignal.WaitAsync();
        await base.StartAsync(cancellationToken);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation("Updating cache.");

            try
            {
                Photo[]? photos =
                    await httpClient.GetFromJsonAsync<Photo[]>(
                        Url, stoppingToken);

                if (photos is { Length: > 0 })
                {
                    cache.Set("Photos", photos);
                    logger.LogInformation(
                        "Cache updated with {Count:#,#} photos.", photos.Length);
                }
                else
                {
                    logger.LogWarning(
                        "Unable to fetch photos to update cache.");
                }
            }
            finally
            {
                if (!_isCacheInitialized)
                {
                    cacheSignal.Release();
                    _isCacheInitialized = true;
                }
            }

            try
            {
                logger.LogInformation(
                    "Will attempt to update the cache in {Hours} hours from now.",
                    _updateInterval.Hours);

                await Task.Delay(_updateInterval, stoppingToken);
            }
            catch (OperationCanceledException)
            {
                logger.LogWarning("Cancellation acknowledged: shutting down.");
                break;
            }
        }
    }
}

V předchozím kódu jazyka C#:

  • Konstruktor vyžaduje ILogger, HttpClient a IMemoryCache.
  • _updateInterval je definován na tři hodiny.
  • Metoda ExecuteAsync :
    • Smyčky, když je aplikace spuštěná.
    • Vytvoří požadavek HTTP na "https://jsonplaceholder.typicode.com/photos"a mapuje odpověď jako pole Photo objektů.
    • Pole fotek je umístěno do IMemoryCache pod klíčem "Photos".
    • Je zavolán _cacheSignal.Release(), čímž se uvolní všichni uživatelé, kteří čekali na tento signál.
    • Volání Task.Delay, které je očekáváno, je vzhledem k intervalu aktualizace.
    • Po zpoždění po dobu tří hodin se mezipaměť znovu aktualizuje.

Uživatelé ve stejném procesu mohou požádat IMemoryCache o fotky, ale CacheWorker je zodpovědný za aktualizaci mezipaměti.

Hybridní ukládání do mezipaměti

Knihovna HybridCache kombinuje výhody ukládání do paměti a distribuovaného ukládání do mezipaměti a řeší běžné problémy s existujícími rozhraními API pro ukládání do mezipaměti. Bylo představeno v .NET 9, HybridCache poskytuje jednotné rozhraní API, které zjednodušuje implementaci cachingu a zahrnuje integrované funkce, jako je ochrana proti zátěži a konfigurovatelná serializace.

Klíčové funkce

HybridCache nabízí několik výhod oproti použití IMemoryCache a IDistributedCache samostatně:

  • Dvouúrovňové ukládání do mezipaměti: Automaticky spravuje vrstvy mezipaměti v paměti (L1) i vrstvy distribuované mezipaměti (L2). Data se nejprve načtou z mezipaměti v paměti pro rychlost, pak z distribuované mezipaměti v případě potřeby a nakonec ze zdroje.
  • Ochrana s razítkem: Zabrání několika souběžným požadavkům v provádění stejné nákladné operace. Pouze jeden požadavek načte data, zatímco ostatní čekají na výsledek.
  • Konfigurovatelná serializace: Podporuje více formátů serializace, včetně JSON (výchozí), protobuf a XML.
  • Zneplatnění založené na značkách: Seskupování souvisejících položek mezipaměti se značkami pro efektivní dávkové zneplatnění.
  • Zjednodušené rozhraní API: Metoda GetOrCreateAsync zpracovává neúspěšné ukládání do mezipaměti, serializaci a úložiště automaticky.

Kdy použít HybridCache

Zvažte použití HybridCache v následujících případech:

  • Potřebujete místní (v paměti) i distribuované ukládání do mezipaměti v prostředí s více servery.
  • Chcete ochranu proti scénářům zvýšené zátěže mezipaměti.
  • Preferujete zjednodušené rozhraní API před ručním koordinací IMemoryCache a IDistributedCache.
  • Pro související položky potřebujete zneplatnění mezipaměti založené na značkách.

Návod

Pro jednoserverové aplikace s jednoduchými potřebami ukládání do mezipaměti může být stačit ukládání do mezipaměti v paměti . U víceserverových aplikací, kde není nutná ochrana před náhlým zatížením ani zneplatnění založené na značkách, zvažte distribuované ukládání do mezipaměti.

Nastavení Hybridní mezipaměti

Chcete-li použít HybridCache, nainstalujte balíček NuGet Microsoft.Extensions.Caching.Hybrid:

dotnet add package Microsoft.Extensions.Caching.Hybrid

Zaregistrujte službu HybridCache pomocí DI voláním AddHybridCache:

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHybridCache();

Předchozí kód registruje HybridCache s výchozími možnostmi. Můžete také nakonfigurovat globální možnosti:

var builderWithOptions = Host.CreateApplicationBuilder(args);
builderWithOptions.Services.AddHybridCache(options =>
{
    options.MaximumPayloadBytes = 1024 * 1024; // 1 MB
    options.MaximumKeyLength = 1024;
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(5),
        LocalCacheExpiration = TimeSpan.FromMinutes(2)
    };
});

Základní použití

Primární metodou pro interakci s HybridCache je GetOrCreateAsync. Tato metoda zkontroluje, jestli mezipaměť neobsahuje zadaný klíč, a pokud nebyla nalezena, volá metodu továrny, aby načetla data:

async Task<WeatherData> GetWeatherDataAsync(HybridCache cache, string city)
{
    return await cache.GetOrCreateAsync(
        $"weather:{city}",
        async cancellationToken =>
        {
            // Simulate fetching from an external API
            await Task.Delay(100, cancellationToken);
            return new WeatherData(city, 72, "Sunny");
        }
    );
}

V předchozím kódu jazyka C#:

  • Metoda GetOrCreateAsync přebírá jedinečný klíč a tovární metodu.
  • Pokud data nejsou v mezipaměti, je volána tovární metoda pro jejich načtení.
  • Data se automaticky ukládají v paměti i v distribuovaných mezipamětí.
  • Pouze jeden souběžný požadavek spustí metodu továrny; ostatní čekají na výsledek.

Možnosti zadávání

Globální výchozí hodnoty pro konkrétní položky mezipaměti můžete přepsat pomocí HybridCacheEntryOptions:

async Task<WeatherData> GetWeatherWithOptionsAsync(HybridCache cache, string city)
{
    var entryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(10),
        LocalCacheExpiration = TimeSpan.FromMinutes(5)
    };

    return await cache.GetOrCreateAsync(
        $"weather:{city}",
        async cancellationToken => new WeatherData(city, 72, "Sunny"),
        entryOptions
    );
}

Možnosti zadávání umožňují konfigurovat:

Zneplatnění na základě značek

Značky umožňují seskupit související položky mezipaměti a zneplatnit je dohromady. To je užitečné ve scénářích, kdy je potřeba související data aktualizovat jako jednotku:

async Task<CustomerData> GetCustomerAsync(HybridCache cache, int customerId)
{
    var tags = new[] { "customer", $"customer:{customerId}" };

    return await cache.GetOrCreateAsync(
        $"customer:{customerId}",
        async cancellationToken => new CustomerData(customerId, "John Doe", "john@example.com"),
        new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(30) },
        tags
    );
}

Zrušení platnosti všech položek s konkrétní značkou:

async Task InvalidateCustomerCacheAsync(HybridCache cache, int customerId)
{
    await cache.RemoveByTagAsync($"customer:{customerId}");
}

Můžete také zneplatnit více značek najednou:

async Task InvalidateAllCustomersAsync(HybridCache cache)
{
    await cache.RemoveByTagAsync(new[] { "customer", "orders" });
}

Poznámka:

Invalidace založená na značkách je logická operace. Aktivně neodebere hodnoty z mezipaměti, ale zajistí, aby označené položky byly považovány za chybějící položky mezipaměti. Položky nakonec vyprší na základě jejich nakonfigurované životnosti.

Odebrání položek mezipaměti

Pokud chcete odebrat určitou položku mezipaměti podle klíče, použijte metodu RemoveAsync :

async Task RemoveWeatherDataAsync(HybridCache cache, string city)
{
    await cache.RemoveAsync($"weather:{city}");
}

Pokud chcete zneplatnit všechny položky uložené v mezipaměti, použijte vyhrazenou zástupnou značku "*":

async Task InvalidateAllCacheAsync(HybridCache cache)
{
    await cache.RemoveByTagAsync("*");
}

Serialization

Pro scénáře distribuovaného ukládání do mezipaměti vyžaduje HybridCache serializaci. Ve výchozím nastavení zpracovává string a byte[] interně a používá System.Text.Json pro jiné typy. Můžete nakonfigurovat vlastní serializátory pro konkrétní typy nebo použít serializátor pro obecné účely:

// Custom serialization example
// Note: This requires implementing a custom IHybridCacheSerializer<T>
var builderWithSerializer = Host.CreateApplicationBuilder(args);
builderWithSerializer.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(10),
        LocalCacheExpiration = TimeSpan.FromMinutes(5)
    };
});
// To add a custom serializer, uncomment and provide your implementation:
// .AddSerializer<WeatherData, CustomWeatherDataSerializer>();

Konfigurace distribuované mezipaměti

HybridCache používá nakonfigurovanou IDistributedCache implementaci pro distribuovanou mezipaměť (L2). I bez nakonfigurovaného IDistributedCacheHybridCache stále poskytuje ukládání do mezipaměti v paměti a ochranu proti zahlcení. Přidání Redis jako distribuované mezipaměti:

// Distributed cache with Redis
var builderWithRedis = Host.CreateApplicationBuilder(args);
builderWithRedis.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});
builderWithRedis.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(30),
        LocalCacheExpiration = TimeSpan.FromMinutes(5)
    };
});

Další informace o implementacích distribuované mezipaměti naleznete v tématu Distribuované ukládání do mezipaměti.

Distribuované ukládání do mezipaměti

V některých scénářích se vyžaduje distribuovaná mezipaměť – to je případ několika aplikačních serverů. Distribuovaná mezipaměť umožňuje vyšší škálovatelnost než ukládání do mezipaměti v paměti. Použití distribuované mezipaměti přesměruje paměť mezipaměti do externího procesu, ale vyžaduje další vstupně-výstupní operace sítě a zavádí trochu větší latenci (i když je nominální).

Distribuovaná Microsoft.Extensions.Caching.Memory abstrakce ukládání do mezipaměti jsou součástí balíčku NuGet a existuje dokonce i AddDistributedMemoryCache metoda rozšíření.

Upozornění

AddDistributedMemoryCache by se měla používat pouze ve scénářích vývoje nebo testování a nejedná se o realizovatelnou produkční implementaci.

Vezměte v úvahu některou z dostupných implementací IDistributedCache následujících balíčků:

Distribuované rozhraní API pro ukládání do mezipaměti

Distribuovaná rozhraní API pro ukládání do mezipaměti jsou trochu primitivnější než jejich protějšky rozhraní API pro ukládání do mezipaměti v paměti. Páry klíč-hodnota jsou o něco jednodušší. Klíče pro ukládání do mezipaměti v paměti jsou založeny na object, zatímco distribuované klíče jsou string. Při ukládání do paměti může být hodnota jakýkoli silně typovaný generický typ, zatímco hodnoty v distribuované mezipaměti jsou trvalé jako byte[]. To neznamená, že různé implementace nezpřístupňují silně typované generické hodnoty, ale to je detail implementace.

Vytvoření hodnot

Pokud chcete vytvořit hodnoty v distribuované mezipaměti, zavolejte jedno z nastavených rozhraní API:

AlphabetLetter Pomocí záznamu z příkladu mezipaměti v paměti můžete objekt serializovat do formátu JSON a pak kódovat string jako byte[]:

DistributedCacheEntryOptions options = new()
{
    AbsoluteExpirationRelativeToNow =
        TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
};

AlphabetLetter alphabetLetter = new(letter);
string json = JsonSerializer.Serialize(alphabetLetter);
byte[] bytes = Encoding.UTF8.GetBytes(json);

await cache.SetAsync(letter.ToString(), bytes, options);

Podobně jako u ukládání mezipaměti přímo v paměti mohou mít položky mezipaměti různé volby, které pomáhají doladit jejich existenci v mezipaměti – v tomto případě DistributedCacheEntryOptions.

Vytvoření rozšiřujících metod

Pro vytváření hodnot existuje několik metod rozšíření založených na pohodlí. Tyto metody pomáhají vyhnout se kódování string reprezentací objektů do byte[]:

Čtení hodnot

Pokud chcete číst hodnoty z distribuované mezipaměti, zavolejte jedno z Get rozhraní API:

AlphabetLetter? alphabetLetter = null;
byte[]? bytes = await cache.GetAsync(letter.ToString());
if (bytes is { Length: > 0 })
{
    string json = Encoding.UTF8.GetString(bytes);
    alphabetLetter = JsonSerializer.Deserialize<AlphabetLetter>(json);
}

Jakmile je položka mezipaměti přečtená z mezipaměti, můžete získat reprezentaci kódování string UTF8 z byte[].

Přečíst metody rozšíření

Existuje několik metod rozšíření založených na pohodlí pro čtení hodnot. Tyto metody pomáhají vyhnout se dekódování byte[] do string reprezentací objektů:

Aktualizace hodnot

Neexistuje způsob, jak aktualizovat hodnoty v distribuované mezipaměti jedním voláním rozhraní API. Hodnoty můžou mít resetováno klouzavé vypršení platnosti pomocí některého z rozhraní API pro aktualizaci:

Pokud je potřeba aktualizovat skutečnou hodnotu, musíte tuto hodnotu odstranit a pak ji znovu přidat.

Odstranění hodnot

Pokud chcete odstranit hodnoty v distribuované mezipaměti, zavolejte jedno z Remove rozhraní API:

Návod

I když existují synchronní verze těchto rozhraní API, zvažte skutečnost, že implementace distribuovaných mezipamětí jsou závislé na vstupně-výstupních operacích sítě. Z tohoto důvodu je obvykle vhodnější použít asynchronní rozhraní API.

Viz také