Principes de base d’ASP.NET Core : exploration du cache en mémoire

L’utilisation de la mise en cache en mémoire améliore les performances et l’évolutivité des applications Web. Dans cet article de blog, nous verrons comment implémenter la mise en cache en mémoire et verrons les avantages qu’elle offre dans la pratique.
L’utilisation du cache est essentielle dans les scénarios où une grande quantité de données est lue, car elle rend ces données accessibles et moins coûteuses par rapport aux méthodes traditionnelles telles que l’accès direct à la base de données pour chaque requête.
ASP.NET Core dispose d’excellents mécanismes pour gérer la mise en cache. Dans cet article, nous explorerons en détail ce qu’est la mise en cache en mémoire, comment elle peut être configurée et utilisée dans ASP.NET Core, ainsi que les meilleures pratiques pour gérer efficacement le cache.
Qu’est-ce que la mise en cache ?
En informatique, la mise en cache est une technique qui stocke temporairement les données dans un emplacement facilement accessible, qui peut être en mémoire ou dans une autre source.
Le principal avantage de l’utilisation d’un cache est d’éviter d’accéder au périphérique de stockage standard, comme une base de données, qui peut souvent s’avérer coûteux. Ainsi, les données sont stockées dans une source temporaire plus facile d’accès, ce qui rend l’interrogation de ces données plus rapide et moins coûteuse.
Qu’est-ce que le cache en mémoire ?
ASP.NET Core prend en charge plusieurs types de caches différents. La mémoire cache est la plus simple et est stockée dans la mémoire du serveur Web. Le cache en mémoire peut stocker tout type d’objet au format de paire clé-valeur.
Il est possible d’implémenter la mise en cache en mémoire en utilisant deux approches :
- En utilisant le
System.Runtime.Caching/MemoryCache namespace
fait partie du .NET Framework et fournit une API pour la mise en cache en mémoire. C’est une option solide, en particulier pour gérer le code existant. - En utilisant le
Microsoft.Extensions.Caching.Memory
L’espace de noms est la meilleure approche pour les nouvelles applications ASP.NET Core. Il fait partie des fonctionnalités d’ASP.NET Core et fournit une implémentation plus moderne et flexible de la mise en cache en mémoire.
Le cache en mémoire d’ASP.NET Core stocke temporairement les données dans la mémoire du serveur, ce qui permet de les récupérer rapidement sans effectuer d’opérations de rétablissement, ce qui dans de nombreux cas peut être coûteux.
Lorsque les données sont consultées pour la première fois, elles sont normalement récupérées à partir de sources plus lentes, telles qu’une base de données ou un service externe, puis une copie de ces données est stockée dans la mémoire cache. Si les mêmes données sont demandées à nouveau, le système vérifie si elles se trouvent dans le cache. Si tel est le cas, les données sont délivrées rapidement depuis le cache, évitant ainsi de rechercher les sources.
Enfin, il est possible d’utiliser des politiques d’expiration ou d’invalidation du cache qui agissent périodiquement pour mettre à jour ou supprimer des données du cache, pour assurer la cohérence des données et permettre uniquement aux données les plus pertinentes d’occuper de l’espace.
Implémentation de la mise en cache en mémoire
Dans cet article, nous allons créer une API simple pour gérer les données de température. Nous utiliserons SQL Server pour enregistrer et récupérer ces données, puis nous vérifierons les performances des requêtes à l’aide du cache en mémoire.
Vous pouvez accéder au code complet ici : Code source de l’API de prévisions météorologiques.
Conditions préalables
Vous devez disposer de la dernière version de .NET. Cet article utilise la version 8.0.
Vous avez également besoin d’une connexion SQL Server locale préconfigurée. Vous pouvez utiliser n’importe quelle autre base de données, mais l’exemple de cet article utilise SQL Server avec Entity Framework Core.
Création de l’application et installation des dépendances
Pour créer l’exemple d’application, vous pouvez utiliser la commande suivante :
dotnet new web -n WeatherForecastAPI
Pour installer les packages NuGet, vous pouvez utiliser les commandes ci-dessous :
dotnet add package Microsoft.Extensions.Caching.Memory
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Bogus
Création de l’entité
Ouvrez le projet avec votre IDE préféré. Créez un nouveau dossier appelé « Modèles » et à l’intérieur, créez la classe suivante :
namespace WeatherForecastAPI.Models;
public class WeatherForecast
{
public int Id { get; set; }
public string City { get; set; }
public double TemperatureC { get; set; }
public string Summary { get; set; }
public DateTime Date { get; set; }
}
Création de la classe de contexte
Cet exemple utilise EF Core, nous devons donc créer une classe pour étendre le DbContext
classe. Pour cela, créez un nouveau dossier appelé « Data » et à l’intérieur créez la classe ci-dessous :
using Bogus;
using Microsoft.EntityFrameworkCore;
using WeatherForecastAPI.Models;
namespace WeatherForecastAPI.Data;
public class WeatherForecastDbContext : DbContext
{
public WeatherForecastDbContext(DbContextOptions<WeatherForecastDbContext> options) : base(options) { }
public DbSet<WeatherForecast> WeatherForecasts { get; set; }
public void SeedDatabase(int numberOfRecords)
{
if (WeatherForecasts.Any())
return;
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
var faker = new Faker<WeatherForecast>()
.RuleFor(w => w.City, f => f.Address.City())
.RuleFor(w => w.TemperatureC, f => f.Random.Double(-20, 55))
.RuleFor(w => w.Summary, f => f.PickRandom(summaries))
.RuleFor(w => w.Date, f => f.Date.Past(0));
var weatherData = faker.Generate(numberOfRecords);
WeatherForecasts.AddRange(weatherData);
SaveChanges();
}
}
Notez que nous avons défini ici un contexte de base de données appelé WeatherForecastDbContext
où la méthode SeedDatabase(int numberOfRecords)
est déclaré pour remplir la base de données avec des données factices en utilisant la bibliothèque Bogus.
Tout d’abord, la méthode vérifie s’il existe déjà des enregistrements dans le tableau des prévisions météorologiques. S’ils existent, la méthode revient sans rien faire, empêchant ainsi la duplication des données.
La méthode définit ensuite un ensemble de résumés de prévisions météorologiques, tels que Gel, Chaud et Doux. Ensuite, le Faker<WeatherForecast>
l’instance de classe est configurée pour générer WeatherForecast
objets avec des valeurs aléatoires pour les propriétés ville, température, résumé et date. La température est générée dans une plage spécifique (-20 à 55 degrés Celsius) et la date est aléatoire dans le passé.
Ces données seront utilisées ultérieurement pour vérifier le fonctionnement du cache en mémoire.
Création des configurations de base de données et des points de terminaison de l’API
L’étape suivante consiste à déclarer les paramètres de la chaîne de connexion et le point de terminaison qui implémentera l’utilisation des données dans le cache en mémoire.
Remplacez le Program
code de classe avec le code ci-dessous :
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using WeatherForecastAPI.Data;
using WeatherForecastAPI.Models;
var builder = WebApplication.CreateBuilder(args);
string connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<WeatherForecastDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddMemoryCache();
var app = builder.Build();
app.MapGet("/weather/{city}", async (string city, WeatherForecastDbContext db, IMemoryCache cache) =>
{
WeatherForecast? weather = null;
if (!cache.TryGetValue(city, out weather))
{
weather = await db.WeatherForecasts
.OrderByDescending(w => w.Date)
.FirstOrDefaultAsync(w => w.City == city);
if (weather != null)
{
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
cache.Set(city, weather, cacheEntryOptions);
}
}
return weather == null ? Results.NotFound("Weather data not found for the specified city.") : Results.Ok(weather);
});
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<WeatherForecastDbContext>();
db.Database.Migrate();
db.SeedDatabase(10000);
}
app.Run();
Ici nous avons défini une configuration pour obtenir la chaîne de connexion avec la base de données GetConnectionString("DefaultConnection")
.
Nous avons également ajouté le service de contexte de base de données (WeatherForecastDbContext
) au conteneur d’injection de dépendances, configuré pour utiliser SQL Server avec la chaîne de connexion fournie.
Ensuite nous avons ajouté le service de cache mémoire avec la configuration : builder.Services.AddMemoryCache()
.
Une route API /weather/{city}
est également défini en utilisant app.MapGet
. Cette route accepte le nom d’une ville comme paramètre et utilise les services de base de données et de cache mémoire injectés.
À l’intérieur de l’itinéraire, le code tente d’obtenir les prévisions météorologiques de la mémoire cache. Si la prédiction n’est pas dans le cache, elle interroge la base de données.
Les prévisions les plus récentes pour la ville spécifiée sont obtenues à partir de la base de données en classant les prévisions par date par ordre décroissant et en prenant le premier enregistrement correspondant à la ville. Si la prédiction est trouvée dans la base de données, elle est ajoutée au cache avec une expiration de 10 minutes. La méthode renvoie les prévisions météorologiques si elles sont trouvées, ou un NotFound
réponse.
Le db.Database.Migrate()
La méthode applique toutes les migrations en attente pour s’assurer que la base de données est à jour et que le db.SeedDatabase(10000)
La méthode remplit la base de données avec 10 000 enregistrements de prévisions météorologiques factices si elle est vide.
Notez à quel point il est simple de configurer la recherche de données dans le cache et de l’insérer si elle n’existe pas.
Génération des migrations de bases de données
Avant d’exécuter l’application et de tester la recherche dans le cache, nous devons ajouter la chaîne de connexion au projet et exécuter les migrations EF Core pour générer les schémas de base de données.
Donc, à l’intérieur du fichier appsettings.json
ajoutez le code ci-dessous :
"ConnectionStrings": {
"DefaultConnection": "Server=YOUR_LOCAL_CONNECTION;Database=WeatherForecastDb;Trusted_Connection=True;MultipleActiveResultSets=true"
},
Remplacer le texte YOUR_LOCAL_CONNECTION
avec votre configuration de données locale.
Maintenant, ouvrez un terminal à la racine de l’application et exécutez les commandes de migration EF Core. N’oubliez pas que EF Core doit être installé globalement. Si vous ne l’avez pas déjà, vous pouvez utiliser la commande suivante :
dotnet tool install --global dotnet-ef
Commandes de migration EF Core :
- Créer la migration initiale
dotnet ef migrations add InitialCreate
dotnet ef database update
Test de l’efficacité du cache en mémoire avec Fiddler
Maintenant que nous avons créé et configuré l’application, nous pouvons l’exécuter et vérifier l’efficacité apportée par l’utilisation du cache. Alors, lancez simplement l’application et, via Telerik Un violoniste partoutfaire une demande d’itinéraire /weather/{city}
en utilisant comme paramètre l’une des villes saisies dans la base de données. Notez que la base de données sera renseignée lors de l’exécution de l’application.
Les images ci-dessous illustrent les requêtes adressées à l’API à l’aide de Fiddler. En inspectant les détails dans l’onglet Trafic, nous pouvons voir que la première requête a pris 332 millisecondes. En effet, lors de la première requête, les données n’étaient pas dans le cache ; l’API a donc récupéré les données directement de la base de données, puis les a ajoutées au cache.
La deuxième requête n’a pris que 15 millisecondes, l’API récupérant les données du cache, réduisant ainsi considérablement le temps nécessaire au renvoi des données.
Pour calculer à quel point la deuxième requête (utilisant le cache) est plus efficace que la première, nous pouvons utiliser la formule suivante pour l’efficacité relative :
Efficacité relative = Temps de première demande / Temps de deuxième demande
Efficacité relative = 332 ms / 15 ms
Efficacité relative = 22,13
Cela signifie que la deuxième requête était environ 22,13 fois plus efficace que la première. Dans l’exemple de l’article, une petite quantité de données a été utilisée ; cependant, il existe des scénarios avec des quantités de données importantes dans lesquels le cache s’avère extrêmement efficace pour rendre les requêtes moins coûteuses.
Autres options de gestion de la mise en cache en mémoire
En plus de l’exemple ci-dessus, d’autres possibilités existent pour gérer le cache mémoire, telles que l’insertion, la mise à jour et la suppression du cache.
1. Ajout de villes avec des prévisions météorologiques au cache
Le point de terminaison ci-dessous répertorie les différentes villes et vérifie si elles sont dans le cache ; sinon, ils sont insérés, en configurant un délai d’expiration de 30 minutes. L’ajout d’éléments au cache à l’avance est utile car cela améliore les performances de l’application en réduisant le nombre de requêtes répétitives dans la base de données. Cela accélère le temps de réponse, économise les ressources de la base de données et offre une expérience utilisateur plus efficace.
app.MapGet("/weather/cities", async (WeatherForecastDbContext db, IMemoryCache cache) =>
{
const string cacheKey = "cities_with_weather";
if (!cache.TryGetValue(cacheKey, out List<string> cities))
{
cities = await db.WeatherForecasts
.Select(w => w.City)
.Distinct()
.ToListAsync();
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(30));
cache.Set(cacheKey, cities, cacheEntryOptions);
}
return Results.Ok(cities);
});
2. Mettre à jour le cache manuellement
Il est possible de mettre à jour le cache manuellement. Supposons que vous soyez informé que les données d’une ville ont été mises à jour dans la base de données. Vous pouvez utiliser le point de terminaison ci-dessous pour mettre à jour les données dans le cache.
app.MapPost("/weather/cache-update/{city}", async (string city, WeatherForecastDbContext db, IMemoryCache cache) =>
{
var weather = await db.WeatherForecasts
.OrderByDescending(w => w.Date)
.FirstOrDefaultAsync(w => w.City == city);
if (weather != null)
{
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
cache.Set(city, weather, cacheEntryOptions);
return Results.Ok($"Cache for city {city} updated.");
}
else
return Results.NotFound("Weather data not found for the specified city.");
});
3. Supprimer un élément spécifique du cache
Si, pour une raison quelconque, vous devez supprimer un élément spécifique du cache, vous pourriez avoir un point de terminaison comme celui-ci :
app.MapDelete("/weather/cache-delete/{city}", (string city, IMemoryCache cache) =>
{
cache.Remove(city);
return Results.Ok($"Cache for city {city} removed.");
});
4. Effacer tout le cache
Il est également possible de vider tout le contenu du cache. Pour ce faire, utilisez simplement le Clear()
méthode de la classe MemoryCache.
app.MapDelete("/cache/clear", (IMemoryCache cache) =>
{
if (cache is MemoryCache concreteMemoryCache)
concreteMemoryCache.Clear();
return Results.Ok("Cache cleared.");
});
Conclusion et considérations finales
Dans les scénarios où il existe une grande quantité de données et où ces données doivent être accessibles rapidement, l’ajout d’un mécanisme de mise en cache peut être crucial pour le fonctionnement de l’application et une bonne expérience utilisateur. De plus, l’utilisation du cache évite un accès excessif à la source de données, telle qu’une base de données, ce qui entraîne des économies de ressources.
Dans cet article, nous avons vu comment implémenter la mise en cache en mémoire dans une API ASP.NET Core et comment les requêtes efficaces sont comparées aux requêtes sans utiliser le cache. De plus, nous avons vu comment manipuler le cache en ajoutant, mettant à jour et supprimant des données du cache en mémoire.
Lorsque vous rencontrez des problèmes de performances, essayez d’utiliser le cache en mémoire. Cela peut être une option plus simple et plus efficace pour votre situation.
Source link