Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Dans cet article, vous allez découvrir différents mécanismes de mise en cache. La mise en cache est l’acte de stockage des données dans une couche intermédiaire, ce qui accélère les récupérations de données suivantes. Conceptuellement, la mise en cache est une stratégie d’optimisation des performances et une considération de conception. La mise en cache peut améliorer considérablement les performances des applications en rendant les données peu modifiées (ou coûteuses à récupérer) plus facilement disponibles. Cet article présente trois approches de mise en cache et fournit des exemples de code source pour chacun d’eux :
- Microsoft.Extensions.Caching.Memory : mise en cache en mémoire pour les scénarios à serveur unique
- Microsoft.Extensions.Caching.Hybrid : mise en cache hybride qui combine la mise en mémoire et la mise en cache distribuée avec des fonctionnalités supplémentaires
- Microsoft.Extensions.Caching.Distributed : mise en cache distribuée pour les scénarios multiserveur
Importante
Il existe deux MemoryCache classes dans .NET, l'une dans l'espace de nommage System.Runtime.Caching et l'autre dans l'espace de nommage Microsoft.Extensions.Caching.
Bien que cet article se concentre sur la mise en cache, il n’inclut pas le System.Runtime.Caching package NuGet. Toutes les références à MemoryCache se trouvent dans l’espace de noms Microsoft.Extensions.Caching.
Tous les Microsoft.Extensions.* packages sont prêts pour l’injection de dépendances (DI). Les interfaces IMemoryCache, HybridCache et IDistributedCache peuvent être utilisées comme services.
Mise en cache en mémoire
Dans cette section, vous allez découvrir le package Microsoft.Extensions.Caching.Memory . L'implémentation actuelle du IMemoryCache est une enveloppe autour du ConcurrentDictionary<TKey,TValue>, offrant une API riche en fonctionnalités. Les entrées dans le cache sont représentées par ICacheEntry et peuvent être de n'importe quel type object. La solution de cache en mémoire est idéale pour les applications qui s’exécutent sur un seul serveur, où les données mises en cache louent de la mémoire dans le processus de l’application.
Conseil / Astuce
Pour les scénarios de mise en cache multiserveur, envisagez l’approche de mise en cache distribuée comme alternative à la mise en cache en mémoire.
API de mise en cache en mémoire
Le consommateur du cache a un contrôle sur les expirations glissantes et absolues :
- ICacheEntry.AbsoluteExpiration
- ICacheEntry.AbsoluteExpirationRelativeToNow
- ICacheEntry.SlidingExpiration
La définition d’une expiration entraîne la suppression des entrées dans le cache si elles ne sont pas accessibles dans le délai d’expiration. Les consommateurs disposent d’options supplémentaires pour contrôler les entrées du cache, via MemoryCacheEntryOptions. Chacun ICacheEntry est associé à MemoryCacheEntryOptions, qui expose les fonctionnalités d'éviction à expiration avec IChangeToken, les paramétrages de priorité avec CacheItemPriority, et le contrôle du ICacheEntry.Size. Les méthodes d’extension pertinentes sont les suivantes :
- MemoryCacheEntryExtensions.AddExpirationToken
- MemoryCacheEntryExtensions.RegisterPostEvictionCallback
- MemoryCacheEntryExtensions.SetSize
- MemoryCacheEntryExtensions.SetPriority
Exemple de cache en mémoire
Pour utiliser l’implémentation par défaut IMemoryCache, appelez la méthode d’extension AddMemoryCache pour inscrire tous les services requis avec DI. Dans l’exemple de code suivant, l’hôte générique est utilisé pour exposer les fonctionnalités d’i 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();
Selon votre charge de travail .NET, vous pourriez accéder différemment à IMemoryCache, comme l’injection de constructeur. Dans cet exemple, vous utilisez l'instance IServiceProvider sur la host et appelez la méthode d’extension générique GetRequiredService<T>(IServiceProvider).
IMemoryCache cache =
host.Services.GetRequiredService<IMemoryCache>();
Avec les services de mise en cache en mémoire inscrits et résolus par le biais de DI, vous êtes prêt à commencer la mise en cache. Cet exemple itère les lettres de l’alphabet anglais « A » à « Z ». Le record AlphabetLetter type contient la référence à la lettre et génère un message.
file record AlphabetLetter(char Letter)
{
internal string Message =>
$"The '{Letter}' character is the {Letter - 64} letter in the English alphabet.";
}
Conseil / Astuce
Le file modificateur d’accès est utilisé sur le AlphabetLetter type, car il est défini dans le fichier Program.cs et accessible uniquement. Pour plus d’informations, consultez le fichier (référence C#). Pour afficher le code source complet, consultez la section Program.cs .
L’exemple inclut une fonction d’assistance qui itère à travers les lettres alphabétiques :
static async ValueTask IterateAlphabetAsync(
Func<char, Task> asyncFunc)
{
for (char letter = 'A'; letter <= 'Z'; ++letter)
{
await asyncFunc(letter);
}
Console.WriteLine();
}
Dans le code C# précédent :
- le
Func<char, Task> asyncFuncest attendu à chaque itération, en passant leletteractuel. - Une fois toutes les lettres traitées, une ligne vide est écrite dans la console.
Pour ajouter des éléments au cache, appelez l’une des API suivantes : Create ou 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;
Dans le code C# précédent :
- La variable
addLettersToCacheTaskdélègue àIterateAlphabetAsyncet est attendue. - Le
Func<char, Task> asyncFuncest argumenté avec un lambda. - Le
MemoryCacheEntryOptionsest instancié avec une expiration absolue par rapport à maintenant. - Un rappel post-éviction est enregistré.
- Un
AlphabetLetterobjet est instancié et passé dans Set avecletteretoptions. - La lettre est écrite dans la console comme étant mise en cache.
- Enfin, un Task.Delay est retourné.
Pour chaque lettre de l’alphabet, une entrée de cache est écrite avec un rappel lors de l'expiration et après l'éviction.
Le rappel post-éviction écrit les détails de la valeur qui a été supprimée dans la 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}.");
}
};
Maintenant que le cache est rempli, un autre appel à IterateAlphabetAsync est attendu, mais cette fois vous appelez 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;
Si la cache contient la clé letter, et que value est une instance de AlphabetLetter, elle est écrite dans la console. Lorsque la clé letter n’est pas dans le cache, elle a été évincée et son callback après éviction a été déclenché.
Méthodes d’extension supplémentaires
Le IMemoryCache est accompagné de nombreuses méthodes d'extension pratiques, y compris une méthode asynchrone GetOrCreateAsync:
- CacheExtensions.Get
- CacheExtensions.GetOrCreate
- CacheExtensions.GetOrCreateAsync
- CacheExtensions.Set
- CacheExtensions.TryGetValue
Mets tout ensemble
L’exemple de code source d’application entier est un programme de niveau supérieur et nécessite deux packages 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.";
}
Vous pouvez ajuster les valeurs de MillisecondsDelayAfterAdd et MillisecondsAbsoluteExpiration pour observer les changements de comportement concernant l’expiration et l’éviction des entrées mises en cache. Voici un exemple de sortie de l’exécution de ce code. (En raison de la nature non déterministe des événements .NET, votre sortie peut être différente.)
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.
Étant donné que l’expiration absolue (MemoryCacheEntryOptions.AbsoluteExpirationRelativeToNow) est définie, tous les éléments mis en cache seront finalement supprimés.
Mise en cache du service Worker
Une stratégie courante pour la mise en cache des données consiste à mettre à jour le cache indépendamment de l’utilisation des services de données. Le modèle de service Worker est un excellent exemple, car il BackgroundService s'exécute indépendamment (ou en arrière-plan) du reste du code d'application. Lorsqu’une application démarre en cours d’exécution qui héberge une implémentation du IHostedService, l’implémentation correspondante (dans ce cas, le BackgroundService « worker ») commence à s’exécuter dans le même processus. Ces services hébergés sont inscrits auprès de DI en tant que singletons, par le biais de la méthode d'extension AddHostedService<THostedService>(IServiceCollection). D’autres services peuvent être inscrits auprès d’un DI avec n’importe quelle durée de vie du service.
Importante
Les durées de vie du service sont importantes à comprendre. Lorsque vous appelez AddMemoryCache pour inscrire tous les services de mise en cache en mémoire, les services sont inscrits en tant que singletons.
Scénario de service photo
Imaginez que vous développez un service photo qui s’appuie sur l’API tierce accessible via HTTP. Ces données photo ne changent pas souvent, mais il y en a beaucoup. Chaque photo est représentée par un simple record:
namespace CachingExamples.Memory;
public readonly record struct Photo(
int AlbumId,
int Id,
string Title,
string Url,
string ThumbnailUrl);
Dans l’exemple suivant, vous voyez plusieurs services enregistrés avec DI. Chaque service a une responsabilité unique.
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();
Dans le code C# précédent :
- L’hôte générique est créé avec les valeurs par défaut.
- Les services de mise en cache en mémoire sont enregistrés auprès de AddMemoryCache.
- Une instance
HttpClientest inscrite pour la classeCacheWorkeravec AddHttpClient<TClient>(IServiceCollection). - La
CacheWorkerclasse est enregistrée auprès de AddHostedService<THostedService>(IServiceCollection). - La
PhotoServiceclasse est enregistrée auprès de AddScoped<TService>(IServiceCollection). - La
CacheSignal<T>classe est enregistrée auprès de AddSingleton. - Il
hostest instancié à partir du générateur et démarré de façon asynchrone.
Le PhotoService est responsable de l’obtention de photos qui correspondent à des critères donnés (ou 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();
}
}
}
Dans le code C# précédent :
- Le constructeur nécessite un
IMemoryCache,CacheSignal<Photo>etILogger. - La méthode
GetPhotosAsync:- Définit un
Func<Photo, bool> filterparamètre et retourne unIAsyncEnumerable<Photo>. - Appelle et attend que le
_cacheSignal.WaitAsync()soit libéré ; ceci garantit que le cache est peuplé avant d’accéder au cache. - Appelle
_cache.GetOrCreateAsync(), obtenant de manière asynchrone toutes les photos dans le cache. - L’argument
factoryenregistre un avertissement et retourne un tableau de photos vide . Cela ne doit jamais se produire. - Chaque photo dans le cache est itérée, filtrée et matérialisée avec
yield return. - Enfin, le signal de cache est réinitialisé.
- Définit un
Les utilisateurs de ce service sont libres d'appeler la méthode GetPhotosAsync pour gérer les photos en conséquence. Aucune HttpClient n'est requise, car le cache contient les photos.
Le signal asynchrone est basé sur une instance encapsulée SemaphoreSlim , au sein d’un singleton limité de type générique. Le CacheSignal<T> repose sur une instance de 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();
}
Dans le code C# précédent, le modèle décoratif est utilisé pour encapsuler une instance du SemaphoreSlim. Étant donné que le CacheSignal<T> est inscrit en tant que singleton, il peut être utilisé sur toutes les durées de vie du service avec n’importe quel type générique, dans ce cas, le Photo. Il est responsable de la signalisation de l’amorçage du cache.
Il CacheWorker s’agit d’une sous-classe de 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;
}
}
}
}
Dans le code C# précédent :
- Le constructeur nécessite un
ILogger,HttpClientetIMemoryCache. - La
_updateIntervalvaleur est définie pendant trois heures. - La méthode
ExecuteAsync:- Boucles pendant l’exécution de l’application.
- Effectue une requête HTTP vers
"https://jsonplaceholder.typicode.com/photos"et renvoie la réponse sous forme de tableau d'objetsPhoto. - Le tableau de photos est placé dans
IMemoryCache, sous la clé"Photos". - Le
_cacheSignal.Release()est appelé, libérant tous les contrôles serveur consommateur qui attendaient le signal. - L’appel à Task.Delay est attendu, compte tenu de l’intervalle de mise à jour.
- Après un délai de trois heures, le cache est à nouveau mis à jour.
Les contrôles serveur consommateur dans le même processus peuvent demander le IMemoryCache pour les photos, mais le CacheWorker est responsable de la mise à jour du cache.
Mise en cache hybride
La HybridCache bibliothèque combine les avantages de la mise en mémoire et de la mise en cache distribuée tout en répondant aux défis courants liés aux API de mise en cache existantes. Introduit dans .NET 9, HybridCache fournit une API unifiée qui simplifie l’implémentation de la mise en cache et inclut des fonctionnalités intégrées telles que la protection contre l'afflux et la sérialisation configurable.
Fonctionnalités clés
HybridCache offre plusieurs avantages par rapport à l’utilisation IMemoryCache et IDistributedCache séparément :
- Mise en cache à deux niveaux : gère automatiquement les couches de cache en mémoire (L1) et distribuées (L2). Les données sont extraites du cache en mémoire en premier pour la vitesse, puis à partir du cache distribué si nécessaire, et enfin à partir de la source.
- Protection contre les embouteillages : empêche plusieurs requêtes simultanées d'exécuter la même opération coûteuse. Une seule requête extrait les données pendant que d’autres attendent le résultat.
- Sérialisation configurable : prend en charge plusieurs formats de sérialisation, notamment JSON (par défaut), protobuf et XML.
- Invalidation basée sur les balises: regroupe les entrées de cache associées avec des balises pour une invalidation de lot efficace.
-
API simplifiée : la
GetOrCreateAsyncméthode gère automatiquement les absences de cache, la sérialisation et le stockage.
Quand utiliser HybridCache
Envisagez d’utiliser HybridCache quand :
- Vous avez besoin de la mise en cache locale (en mémoire) et de la mise en cache distribuée dans un environnement multi-serveur.
- Vous souhaitez une protection contre les scénarios de tempête de cache.
- Vous préférez une API simplifiée plutôt que de coordonner
IMemoryCacheetIDistributedCachemanuellement. - Vous avez besoin d’une invalidation de cache basée sur des balises pour les entrées associées.
Conseil / Astuce
Pour les applications à serveur unique avec des besoins de mise en cache simples, la mise en cache en mémoire peut être suffisante. Pour les applications multi-serveurs sans avoir besoin d'une protection contre l'engorgement ou d'une invalidation par balises, envisagez la mise en cache distribuée.
Configuration de HybridCache
Pour utiliser HybridCache, installez le Microsoft.Extensions.Caching.Hybrid package NuGet :
dotnet add package Microsoft.Extensions.Caching.Hybrid
Inscrivez le service avec l'ID HybridCache en appelant AddHybridCache :
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHybridCache();
Le code précédent enregistre HybridCache avec les options par défaut. Vous pouvez également configurer des options globales :
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)
};
});
Utilisation de base
La méthode principale pour interagir avec HybridCache est GetOrCreateAsync. Cette méthode vérifie le cache d’une entrée avec la clé spécifiée et, si elle est introuvable, appelle la méthode de fabrique pour récupérer les données :
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");
}
);
}
Dans le code C# précédent :
- La
GetOrCreateAsyncméthode prend une clé unique et une méthode de fabrication. - Si les données ne se trouvent pas dans le cache, la méthode de fabrique est appelée pour la récupérer.
- Les données sont automatiquement stockées dans les caches en mémoire et distribués.
- Une seule requête simultanée exécute la méthode de fabrique ; d’autres attendent le résultat.
Options d’entrée
Vous pouvez remplacer les valeurs par défaut globales pour des entrées de cache spécifiques à l’aide HybridCacheEntryOptionsde :
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
);
}
Les options d’entrée vous permettent de configurer :
- HybridCacheEntryOptions.Expiration: durée pendant laquelle l’entrée doit être mise en cache dans le cache distribué.
- HybridCacheEntryOptions.LocalCacheExpiration: durée pendant laquelle l’entrée doit être mise en cache dans la mémoire locale.
- HybridCacheEntryOptions.Flags: indicateurs supplémentaires pour contrôler le comportement du cache.
Invalidation basée sur des balises
Les balises vous permettent de regrouper les entrées de cache associées et de les invalider ensemble. Cela est utile pour les scénarios où les données associées doivent être actualisées en tant qu’unité :
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
);
}
Pour invalider toutes les entrées avec une balise spécifique :
async Task InvalidateCustomerCacheAsync(HybridCache cache, int customerId)
{
await cache.RemoveByTagAsync($"customer:{customerId}");
}
Vous pouvez également invalider plusieurs balises à la fois :
async Task InvalidateAllCustomersAsync(HybridCache cache)
{
await cache.RemoveByTagAsync(new[] { "customer", "orders" });
}
Note
L’invalidation basée sur des balises est une opération logique. Elle ne supprime pas activement les valeurs du cache, mais garantit que les entrées marquées sont traitées comme des absences de cache. Les entrées expirent finalement en fonction de leur durée de vie configurée.
Supprimer les entrées du cache
Pour supprimer une entrée de cache spécifique par clé, utilisez la RemoveAsync méthode :
async Task RemoveWeatherDataAsync(HybridCache cache, string city)
{
await cache.RemoveAsync($"weather:{city}");
}
Pour invalider toutes les entrées mises en cache, utilisez la balise "*"générique réservée :
async Task InvalidateAllCacheAsync(HybridCache cache)
{
await cache.RemoveByTagAsync("*");
}
Sérialisation
Pour les scénarios de mise en cache distribuée, HybridCache nécessite la sérialisation. Par défaut, il gère string et byte[] en interne, et il utilise System.Text.Json pour d’autres types. Vous pouvez configurer des sérialiseurs personnalisés pour des types spécifiques ou utiliser un sérialiseur à usage général :
// 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>();
Configurer le cache distribué
HybridCache utilise l’implémentation configurée IDistributedCache pour son cache distribué (L2). Même sans une IDistributedCache configurée, HybridCache fournit toujours une mise en cache en mémoire et une protection contre l'emballement. Pour ajouter Redis en tant que cache distribué :
// 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)
};
});
Pour plus d’informations sur les implémentations de cache distribué, consultez Mise en cache distribuée.
Mise en cache distribuée
Dans certains scénarios, un cache distribué est requis, c’est-à-dire avec plusieurs serveurs d’applications. Un cache distribué prend en charge une évolutivité horizontale supérieure à l'approche de mise en cache en mémoire vive. L’utilisation d’un cache distribué décharge la mémoire du cache dans un processus externe, mais nécessite des E/S réseau supplémentaires et introduit un peu plus de latence (même si nominale).
Les abstractions de mise en cache distribuée font partie du Microsoft.Extensions.Caching.Memory package NuGet, et il existe même une méthode d’extension AddDistributedMemoryCache .
Avertissement
AddDistributedMemoryCache ne doit être utilisé que dans les scénarios de développement ou de test et n’est pas une implémentation de production viable.
Considérez l’une des implémentations disponibles de IDistributedCache à partir des packages suivants :
Microsoft.Extensions.Caching.SqlServerMicrosoft.Extensions.Caching.StackExchangeRedisNCache.Microsoft.Extensions.Caching.OpenSource
API de mise en cache distribuée
Les API de mise en cache distribuée sont un peu plus primitives que leurs équivalents d’API de mise en cache en mémoire. Les paires clé-valeur sont un peu plus simples. Les clés de mise en cache en mémoire sont basées sur un object, tandis que les clés distribuées sont un string. Avec la mise en cache en mémoire, la valeur peut être n’importe quel générique fortement typé, tandis que les valeurs dans la mise en cache distribuée sont conservées en tant que byte[]. Cela ne veut pas dire que différentes implémentations n’exposent pas de valeurs génériques fortement typées, mais c’est un détail d’implémentation.
Créer des valeurs
Pour créer des valeurs dans le cache distribué, appelez l’une des API set :
À l’aide de l’enregistrement AlphabetLetter à partir de l’exemple de cache en mémoire, vous pouvez sérialiser l’objet au format JSON, puis encoder l’objet string en tant que :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);
Tout comme la mise en cache en mémoire, les entrées de cache peuvent avoir des options pour optimiser leur présence dans le cache, dans ce cas, le DistributedCacheEntryOptions.
Créer des méthodes d’extension
Il existe plusieurs méthodes d’extension basées sur la commodité pour créer des valeurs. Ces méthodes permettent d’éviter l’encodage string des représentations d’objets dans un byte[]:
Lire les valeurs
Pour lire des valeurs à partir du cache distribué, appelez l’une Get des 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);
}
Une fois qu’une entrée de cache est lue depuis le cache, vous pouvez obtenir la représentation encodée UTF8 string à partir du byte[].
Lire les méthodes d’extension
Il existe plusieurs méthodes d’extension basées sur la commodité pour lire les valeurs. Ces méthodes permettent d’éviter le décodage byte[] en string représentations d’objets :
Mettre à jour les valeurs
Il n’existe aucun moyen de mettre à jour les valeurs dans le cache distribué avec un seul appel d’API. Au lieu de cela, les valeurs peuvent avoir leurs expirations glissantes réinitialisées avec l’une des API d’actualisation :
Si la valeur réelle doit être mise à jour, vous devez supprimer la valeur, puis la rajouter.
Supprimer des valeurs
Pour supprimer des valeurs dans le cache distribué, appelez l’une Remove des API :
Conseil / Astuce
Bien qu’il existe des versions synchrones de ces API, tenez compte du fait que les implémentations de caches distribués dépendent des E/S réseau. Pour cette raison, il est généralement préférable d’utiliser les API asynchrones.