Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
In dit artikel leert u meer over verschillende cachemechanismen. Caching is het opslaan van gegevens in een tussenliggende laag, waardoor volgende gegevens sneller worden opgehaald. Conceptueel is caching een strategie voor prestatieoptimalisatie en ontwerpoverwegingen. Caching kan de prestaties van apps aanzienlijk verbeteren door onregelmatig veranderende (of dure) gegevens beter beschikbaar te maken. In dit artikel worden drie methoden voor caching geïntroduceerd en wordt voorbeeldbroncode geboden voor elk van deze methoden:
- Microsoft.Extensions.Caching.Memory: In-memory caching voor scenario's met één server
- Microsoft.Extensions.Caching.Hybrid: Hybride caching die in-memory en gedistribueerde caching combineert met extra functies
- Microsoft.Extensions.Caching.Distributed: Gedistribueerde caching voor scenario's met meerdere servers
Belangrijk
Er zijn twee MemoryCache klassen in .NET, een in de System.Runtime.Caching naamruimte en de andere in de Microsoft.Extensions.Caching naamruimte:
Hoewel dit artikel is gericht op caching, bevat dit niet het System.Runtime.Caching NuGet-pakket. Alle verwijzingen naar MemoryCache bevinden zich in de Microsoft.Extensions.Caching naamruimte.
Microsoft.Extensions.* Alle pakketten zijn klaar voor afhankelijkheidsinjectie (DI). De IMemoryCache, HybridCacheen IDistributedCache interfaces kunnen worden gebruikt als services.
Caching in het geheugen
In deze sectie krijgt u meer informatie over het pakket Microsoft.Extensions.Caching.Memory . De huidige implementatie van de IMemoryCache is een wrapper rond de ConcurrentDictionary<TKey,TValue>, die een uitgebreide API biedt aan. Vermeldingen in de cache worden vertegenwoordigd door de ICacheEntry en kunnen elk objectzijn. De in-memory cacheoplossing is ideaal voor apps die op één server draaien, waarbij de gecachete gegevens geheugen gebruiken binnen het proces van de app.
Aanbeveling
Voor cachescenario's met meerdere servers kunt u overwegen om gedistribueerde caching te gebruiken als alternatief voor cacheopslag in het geheugen.
Cache-API in het geheugen
De consument van de cache heeft controle over zowel verschuivende als absolute vervaldatums:
- ICacheEntry.AbsoluteExpiration
- ICacheEntry.AbsoluteExpirationRelativeToNow
- ICacheEntry.SlidingExpiration
Als u een verlooptijd instelt, worden vermeldingen in de cache verwijderd als ze niet worden geopend binnen de vervaldatum. Consumenten hebben extra opties voor het beheren van cachevermeldingen via MemoryCacheEntryOptions. Elk ICacheEntry is gekoppeld aan MemoryCacheEntryOptions, waarmee de functionaliteit voor het verwijderen van vervaldatums beschikbaar wordt gemaakt met IChangeToken, prioriteitsinstellingen met CacheItemPriorityen het beheren van de ICacheEntry.Size. De relevante extensiemethoden zijn:
- MemoryCacheEntryExtensions.AddExpirationToken
- MemoryCacheEntryExtensions.RegisterPostEvictionCallback
- MemoryCacheEntryExtensions.SetSize
- MemoryCacheEntryExtensions.SetPriority
Voorbeeld van cache in het geheugen
Als u de standaard IMemoryCache implementatie wilt gebruiken, roept u de AddMemoryCache extensiemethode aan om alle vereiste services bij DI te registreren. In het volgende codevoorbeeld wordt de algemene host gebruikt om DI-functionaliteit beschikbaar te maken:
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();
Afhankelijk van uw .NET-workload kunt u op verschillende manieren toegang krijgen tot de IMemoryCache, zoals door middel van constructorinjectie. In dit voorbeeld gebruikt u het IServiceProvider exemplaar met de host en roept u de algemene GetRequiredService<T>(IServiceProvider) extensiemethode aan.
IMemoryCache cache =
host.Services.GetRequiredService<IMemoryCache>();
Wanneer cacheservices in het geheugen zijn geregistreerd en opgelost door DI, kunt u beginnen met het gebruik van caching. In dit voorbeeld worden de letters in het Engelse alfabet 'A' tot en met Z herhaald. Het record AlphabetLetter type bevat de verwijzing naar de brief en genereert een bericht.
file record AlphabetLetter(char Letter)
{
internal string Message =>
$"The '{Letter}' character is the {Letter - 64} letter in the English alphabet.";
}
Aanbeveling
De file toegangsaanpassing wordt gebruikt voor het AlphabetLetter type, omdat deze is gedefinieerd binnen en alleen toegankelijk is vanuit het Program.cs-bestand . Zie bestand (C#-verwijzing) voor meer informatie. Als u de volledige broncode wilt zien, raadpleegt u de sectie Program.cs .
Het voorbeeld bevat een helperfunctie die door de letters van het alfabet iterereert.
static async ValueTask IterateAlphabetAsync(
Func<char, Task> asyncFunc)
{
for (char letter = 'A'; letter <= 'Z'; ++letter)
{
await asyncFunc(letter);
}
Console.WriteLine();
}
In de voorgaande C#-code:
- De
Func<char, Task> asyncFuncwordt op elke iteratie verwacht, waarbij de huidigeletterwordt doorgegeven. - Nadat alle letters zijn verwerkt, wordt er een lege regel naar de console geschreven.
Als u items wilt toevoegen aan de cache, roept u een van de Create, of Set API's aan:
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;
In de voorgaande C#-code:
- De variabele
addLettersToCacheTaskdelegeert aanIterateAlphabetAsyncen wordt afgewacht. - Het
Func<char, Task> asyncFuncwordt aangevoerd met een lambda. - De
MemoryCacheEntryOptionsinstantie wordt geïnstantieerd met een absolute vervaldatum vanaf het huidige moment. - Er wordt een terugroepfunctie na uitzetting geregistreerd.
- Een
AlphabetLetterobject wordt geïnstantieerd en samen met Set enletterdoorgegeven aanoptions. - De letter wordt naar de console geschreven omdat deze in de cache wordt opgeslagen.
- Ten slotte wordt er een Task.Delay geretourneerd.
Voor elke letter in het alfabet wordt een cache-item geschreven met een vervaldatum en een callback na verwijdering.
De callback na uitdrijving schrijft de details van de verwijderde waarde naar de console.
static void OnPostEviction(
object key, object? letter, EvictionReason reason, object? state)
{
if (letter is AlphabetLetter alphabetLetter)
{
Console.WriteLine($"{alphabetLetter.Letter} was evicted for {reason}.");
}
};
Nu de cache is gevuld, wordt er nog een aanroep naar IterateAlphabetAsync verwacht, maar deze keer roept u IMemoryCache.TryGetValue aan.
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;
Als de cache de letter sleutel bevat en het value een instantie is van een AlphabetLetter, wordt het naar de console geschreven. Wanneer de letter sleutel zich niet in de cache bevindt, is deze verwijderd en is de callback na verwijdering aangeroepen.
Aanvullende uitbreidingsmethoden
De IMemoryCache wordt geleverd met tal van gebruiksvriendelijke uitbreidingsmethoden, waaronder een asynchrone GetOrCreateAsync.
- CacheExtensions.Get
- CacheExtensions.GetOrCreate
- CacheExtensions.GetOrCreateAsync
- CacheExtensions.Set
- CacheExtensions.TryGetValue
Alles samenvoegen
De volledige broncode van de voorbeeld-app is een programma op het hoogste niveau en vereist twee NuGet-pakketten:
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.";
}
U kunt de MillisecondsDelayAfterAdd en MillisecondsAbsoluteExpiration waarden aanpassen om de veranderingen in het gedrag te observeren met betrekking tot het verlopen en verwijderen van in de cache opgeslagen items. Hier volgt voorbeelduitvoer van het uitvoeren van deze code. (Vanwege de niet-deterministische aard van .NET-gebeurtenissen kan uw uitvoer afwijken.)
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.
Omdat de absolute vervaldatum (MemoryCacheEntryOptions.AbsoluteExpirationRelativeToNow) is ingesteld, worden alle items in de cache uiteindelijk verwijderd.
Werkrolservice opslaan in cache
Een veelvoorkomende strategie voor het opslaan van gegevens in cache is het bijwerken van de cache onafhankelijk van de verbruikende gegevensservices. De Worker Service-sjabloon is een goed voorbeeld, omdat de BackgroundService onafhankelijk (of op de achtergrond) wordt uitgevoerd van de andere toepassingscode. Wanneer een toepassing wordt uitgevoerd die als host fungeert voor een implementatie van de IHostedServicetoepassing, wordt de bijbehorende implementatie (in dit geval de BackgroundService of 'worker') in hetzelfde proces uitgevoerd. Deze gehoste services worden via de AddHostedService<THostedService>(IServiceCollection) extensiemethode geregistreerd bij DI als singletons. Andere services kunnen worden geregistreerd bij DI bij elke levensduur van de service.
Belangrijk
De levensduur van de service is belangrijk om te begrijpen. Wanneer u AddMemoryCache aanroept om alle in-memory cacheservices te registreren, worden de services als singletons geregistreerd.
Scenario fotoservice
Stel dat u een fotoservice ontwikkelt die afhankelijk is van API van derden die toegankelijk is via HTTP. Deze fotogegevens veranderen niet vaak, maar er zijn er veel. Elke foto wordt vertegenwoordigd door een eenvoudig record:
namespace CachingExamples.Memory;
public readonly record struct Photo(
int AlbumId,
int Id,
string Title,
string Url,
string ThumbnailUrl);
In het volgende voorbeeld ziet u dat er verschillende services worden geregistreerd bij DI. Elke service heeft één verantwoordelijkheid.
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();
In de voorgaande C#-code:
- De algemene host wordt gemaakt met standaardwaarden.
- Cacheservices in het geheugen worden geregistreerd bij AddMemoryCache.
- Een
HttpClientexemplaar is geregistreerd voor deCacheWorkerklasse met AddHttpClient<TClient>(IServiceCollection). - De
CacheWorkerklasse is geregistreerd bij AddHostedService<THostedService>(IServiceCollection). - De
PhotoServiceklasse is geregistreerd bij AddScoped<TService>(IServiceCollection). - De
CacheSignal<T>klasse is geregistreerd bij AddSingleton. - De
hostwordt geïnstantieerd vanuit de builder en wordt asynchroon gestart.
Het PhotoService is verantwoordelijk voor het verkrijgen van foto's die voldoen aan bepaalde criteria (of 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();
}
}
}
In de voorgaande C#-code:
- De constructor vereist een
IMemoryCache,CacheSignal<Photo>enILogger. - De methode
GetPhotosAsync:- Definieert een
Func<Photo, bool> filterparameter en retourneert eenIAsyncEnumerable<Photo>. - Roept aan en wacht tot de
_cacheSignal.WaitAsync()release is uitgevoerd. Dit zorgt ervoor dat de cache wordt gevuld voordat de cache wordt geopend. - Voert de oproep
_cache.GetOrCreateAsync()uit, asynchroon alle foto's in de cache ophalen. - Het
factoryargument registreert een waarschuwing en retourneert een lege fotomatrix. Dit zou nooit moeten gebeuren. - Elke foto in de cache wordt geïtereerd, gefilterd en gematerialiseerd met
yield return. - Ten slotte wordt het cachesignaal opnieuw ingesteld.
- Definieert een
Consumenten van deze service zijn vrij om de methode GetPhotosAsync aan te roepen en foto's dienovereenkomstig te beheren. Er is geen HttpClient vereiste omdat de cache de foto's bevat.
Het asynchrone signaal is gebaseerd op een ingekapseld SemaphoreSlim exemplaar binnen een beperkte singleton van een algemeen type. Het CacheSignal<T> steunt op een instantie van 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();
}
In de voorgaande C#-code wordt het decoratorpatroon gebruikt om een voorbeeld van de SemaphoreSlim te omhullen. Omdat het CacheSignal<T> is geregistreerd als een singleton, kan deze worden gebruikt voor alle levensduur van de service met elk algemeen type, in dit geval de Photo. Het is verantwoordelijk voor het signaleren van de seeding van de cache.
Het CacheWorker is een subklasse van 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;
}
}
}
}
In de voorgaande C#-code:
- De constructor vereist een
ILogger,HttpClientenIMemoryCache. - De
_updateIntervalis vastgesteld voor drie uur. - De methode
ExecuteAsync:- Lussen terwijl de app wordt uitgevoerd.
- Hiermee wordt een HTTP-aanvraag verzonden naar
"https://jsonplaceholder.typicode.com/photos"en wordt het antwoord toegewezen als een array vanPhotoobjecten. - De array met foto's wordt geplaatst in
IMemoryCacheonder de sleutel"Photos". - Het
_cacheSignal.Release()wordt aangeroepen, waarmee alle gebruikers die op het signaal wachtten, worden vrijgegeven. - De aanroep naar Task.Delay wordt afgewacht, gezien het update-interval.
- Na een vertraging van drie uur wordt de cache opnieuw bijgewerkt.
Consumenten in hetzelfde proces kunnen om de IMemoryCache foto's vragen, maar de CacheWorker is verantwoordelijk voor het bijwerken van de cache.
Hybride caching
De HybridCache bibliotheek combineert de voordelen van in-memory en gedistribueerde caching en biedt een oplossing voor veelvoorkomende uitdagingen met bestaande cache-API's. Geïntroduceerd in .NET 9 biedt HybridCache een geïntegreerde API die de implementatie van caching vereenvoudigt en ingebouwde functies bevat, zoals stampede-beveiliging en configureerbare serialisatie.
Belangrijkste kenmerken
HybridCache biedt verschillende voordelen ten opzichte van het gebruik IMemoryCache en IDistributedCache afzonderlijk:
- Cachelagen op twee niveaus: beheert automatisch lagen in het geheugen (L1) en gedistribueerde cachelagen (L2). Gegevens worden eerst opgehaald uit de cache in het geheugen voor snelheid, vervolgens van gedistribueerde cache indien nodig en ten slotte van de bron.
- Stampede-beveiliging: voorkomt dat meerdere gelijktijdige aanvragen dezelfde dure bewerking uitvoeren. Slechts één aanvraag haalt de gegevens op terwijl anderen wachten op het resultaat.
- Configureerbare serialisatie: ondersteunt meerdere serialisatie-indelingen, waaronder JSON (standaard), protobuf en XML.
- Tag-gebaseerde invalidering: Groepen van gerelateerde cachevermeldingen met tags voor efficiënte batch-invalidering.
-
Vereenvoudigde API: Met de
GetOrCreateAsyncmethode worden misses, serialisatie en opslag in de cache automatisch verwerkt.
Wanneer gebruikt u HybridCache?
Overweeg het gebruik van HybridCache wanneer:
- U hebt zowel lokaal (in-memory) als gedistribueerde caching in een omgeving met meerdere servers nodig.
- U wilt beveiliging tegen cachelawine-scenario's.
- U geeft de voorkeur aan een vereenvoudigde API boven handmatige coördinatie van
IMemoryCacheenIDistributedCache. - U hebt tag-gebaseerde cache-invalidatie nodig voor gerelateerde ingangen.
Aanbeveling
Voor toepassingen met één server met eenvoudige cachebehoeften is caching in het geheugen mogelijk voldoende. Overweeg gedistribueerde caching voor toepassingen met meerdere servers zonder stempelbeveiliging of op tags gebaseerde ongeldigheid.
HybridCache instellen
Om HybridCache te gebruiken, installeer het Microsoft.Extensions.Caching.Hybrid NuGet-pakket.
dotnet add package Microsoft.Extensions.Caching.Hybrid
Registreer de HybridCache service bij DI door het aanroepen van AddHybridCache:
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHybridCache();
De voorgaande code registreert HybridCache met de standaardopties. U kunt ook globale opties configureren:
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)
};
});
Basaal gebruik
De primaire methode voor interactie met HybridCache is GetOrCreateAsync. Met deze methode wordt de cache gecontroleerd op een vermelding met de opgegeven sleutel. Als deze niet wordt gevonden, wordt de factorymethode aangeroepen om de gegevens op te halen:
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");
}
);
}
In de voorgaande C#-code:
- De
GetOrCreateAsyncmethode gebruikt een unieke sleutel en een fabrieksmethode. - Als de gegevens zich niet in de cache bevinden, wordt de factorymethode aangeroepen om deze op te halen.
- De gegevens worden automatisch opgeslagen in zowel in-memory als gedistribueerde caches.
- Slechts één gelijktijdige aanvraag voert de factory-methode uit; anderen wachten op het resultaat.
Invoeropties
U kunt algemene standaardinstellingen voor specifieke cachevermeldingen overschrijven met behulp van 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
);
}
Met de invoeropties kunt u het volgende configureren:
- HybridCacheEntryOptions.Expiration: hoelang het item moet worden opgeslagen in de gedistribueerde cache.
- HybridCacheEntryOptions.LocalCacheExpiration: Hoe lang de vermelding in de cache moet worden opgeslagen in het lokale geheugen.
- HybridCacheEntryOptions.Flags: Aanvullende vlaggen voor het beheren van cachegedrag.
Ongeldig maken op basis van tags
Met tags kunt u gerelateerde cachevermeldingen groeperen en ze samen ongeldig maken. Dit is handig voor scenario's waarin gerelateerde gegevens als eenheid moeten worden vernieuwd:
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
);
}
Alle vermeldingen met een specifieke tag ongeldig maken:
async Task InvalidateCustomerCacheAsync(HybridCache cache, int customerId)
{
await cache.RemoveByTagAsync($"customer:{customerId}");
}
U kunt ook meerdere tags tegelijk ongeldig maken:
async Task InvalidateAllCustomersAsync(HybridCache cache)
{
await cache.RemoveByTagAsync(new[] { "customer", "orders" });
}
Opmerking
Ongeldigheid op basis van tags is een logische bewerking. Het systeem verwijdert geen waarden actief uit de cache, maar zorgt ervoor dat gemarkeerde vermeldingen worden behandeld als cache-missers. De vermeldingen verlopen uiteindelijk op basis van hun geconfigureerde levensduur.
Cachevermeldingen verwijderen
Als u een specifieke cachevermelding per sleutel wilt verwijderen, gebruikt u de RemoveAsync methode:
async Task RemoveWeatherDataAsync(HybridCache cache, string city)
{
await cache.RemoveAsync($"weather:{city}");
}
Als u alle vermeldingen in de cache ongeldig wilt maken, gebruikt u de gereserveerde jokertekencode "*":
async Task InvalidateAllCacheAsync(HybridCache cache)
{
await cache.RemoveByTagAsync("*");
}
Serialization
Voor gedistribueerde cachescenario's HybridCache is serialisatie vereist. Standaard worden string en byte[] intern verwerkt en System.Text.Json wordt gebruikt voor andere typen. U kunt aangepaste serializers configureren voor specifieke typen of een serializer voor algemeen gebruik gebruiken:
// 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>();
Gedistribueerde cache configureren
HybridCache maakt gebruik van de geconfigureerde IDistributedCache implementatie voor de gedistribueerde cache (L2). Zelfs zonder een IDistributedCache te configureren, biedt HybridCache nog steeds caching in het geheugen en stampede-bescherming. Redis toevoegen als een gedistribueerde cache:
// 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)
};
});
Zie gedistribueerde cache-implementaties voor meer informatie over gedistribueerde cache-implementaties.
Gedistribueerde caching
In sommige scenario's is een gedistribueerde cache vereist. Dit is het geval bij meerdere app-servers. Een gedistribueerde cache biedt ondersteuning voor een hogere uitschaling dan de in-memory caching-benadering. Als u een gedistribueerde cache gebruikt, wordt het cachegeheugen naar een extern proces verplaatst, maar vereist wel extra netwerk-I/O en introduceert u iets meer latentie (zelfs als nominaal).
De gedistribueerde cachingabstracties maken deel uit van het Microsoft.Extensions.Caching.Memory NuGet-pakket en er is zelfs een AddDistributedMemoryCache extensiemethode.
Waarschuwing
AddDistributedMemoryCache mag alleen worden gebruikt in ontwikkelings- of testscenario's en is geen haalbare productie-implementatie.
Overweeg een van de beschikbare implementaties van de IDistributedCache uit de volgende pakketten:
Microsoft.Extensions.Caching.SqlServerMicrosoft.Extensions.Caching.StackExchangeRedisNCache.Microsoft.Extensions.Caching.OpenSource
Gedistribueerde cache-API
De gedistribueerde cache-API's zijn wat primitiever dan hun in-memory cache-API-tegenhangers. De sleutel-waardeparen zijn iets eenvoudiger. Cachesleutels in het geheugen zijn gebaseerd op een object, terwijl gedistribueerde cachesleutels een string. Met caching in het geheugen kan de waarde een sterk getypeerde algemene waarde zijn, terwijl waarden in gedistribueerde caching behouden blijven als byte[]. Dat wil niet zeggen dat verschillende implementaties geen sterk getypte algemene waarden blootleggen, maar dat is een implementatiedetail.
Waarden maken
Als u waarden wilt maken in de gedistribueerde cache, roept u een van de set-API's aan:
Met behulp van de AlphabetLetter record uit het cachevoorbeeld in het geheugen kunt u het object serialiseren naar JSON en vervolgens de string coderen als: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);
Net als in-memory caching kunnen cache-items opties hebben om hun aanwezigheid in de cache te verfijnen, in dit geval de DistributedCacheEntryOptions.
Extensiemethoden maken
Er zijn verschillende op het gemak gebaseerde uitbreidingsmethoden voor het maken van waarden. Deze methoden helpen om het coderen van objectrepresentaties in een byte[] te vermijden:
Waarden lezen
Als u waarden uit de gedistribueerde cache wilt lezen, roept u een van de Get API's aan:
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);
}
Zodra een cachevermelding uit de cache wordt gelezen, kunt u de UTF8-gecodeerde string weergave ophalen uit de byte[].
Extensiemethoden lezen
Er zijn verschillende op gemak gebaseerde uitbreidingsmethoden voor het lezen van waarden. Deze methoden helpen bij het voorkomen van decodering byte[] in string weergaven van objecten:
Waarden bijwerken
Er is geen manier om de waarden in de gedistribueerde cache bij te werken met één API-aanroep. In plaats daarvan kunnen waarden hun verschuivende vervaldatums opnieuw instellen met een van de vernieuwings-API's:
Als de werkelijke waarde moet worden bijgewerkt, moet u de waarde verwijderen en deze vervolgens opnieuw toevoegen.
Waarden verwijderen
Als u waarden in de gedistribueerde cache wilt verwijderen, roept u een van de Remove API's aan:
Aanbeveling
Hoewel er synchrone versies van deze API's zijn, moet u rekening houden met het feit dat implementaties van gedistribueerde caches afhankelijk zijn van netwerk-I/O. Daarom is het meestal beter om de asynchrone API's te gebruiken.