Fermer

mai 10, 2022

Amélioration des performances avec la mise en cache distribuée

Amélioration des performances avec la mise en cache distribuée


Les applications qui ont une grande quantité de trafic de données sont sujettes à une perte de performances si elles n’utilisent pas un mécanisme pour réduire la consommation élevée d’accès à la base de données. Une façon de résoudre ce problème consiste à utiliser la mise en cache distribuée.

L’utilisation du cache distribué peut être un excellent choix pour travailler avec des applications qui transfèrent une grande quantité de données, car grâce à cela, nous pouvons réduire considérablement la charge de travail, ce qui entraîne des gains de performances.

Dans cet article, nous allons voir en pratique comment améliorer les performances d’une application ASP.NET Core avec la mise en cache distribuée.

Qu’est-ce qu’un cache distribué ?

Un cache distribué est un cache partagé par plusieurs serveurs d’applications. Il est généralement géré en tant que service externe et est disponible pour toutes les applications serveur.

Un cache distribué présente plusieurs avantages et peut améliorer les performances et l’évolutivité d’une application ASP.NET Core. Certains de ces avantages sont :

  • Données cohérentes (cohérentes) entre les demandes adressées à plusieurs serveurs.
  • Survit aux redémarrages du serveur et aux déploiements d’applications.
  • N’utilise pas la mémoire locale.

Cet article utilise Redis pour configurer la mise en cache distribuée, mais d’autres ressources peuvent également être utilisées.

Qu’est-ce que Redis ?

Redis est un magasin de structure de données en mémoire open source (sous licence BSD) utilisé comme base de données, cache et courtier de messages.

Redis fournit de nombreuses structures de données telles que des chaînes, des hachages, des listes, des requêtes et des types complexes. Il s’agit d’une base de données basée sur NoSQL stockant des données dans un format clé-valeur extrêmement rapide. Avec une grande popularité, Redis est utilisé par des entreprises comme Stackoverflow, Twitter et Github.

Pour l’exemple utilisé dans cet article, Redis est un excellent choix, car il permet d’implémenter un cache à haute disponibilité et de réduire la latence d’accès aux données et d’améliorer le temps de réponse des applications.

Installation de Redis

Il existe deux façons d’exécuter Redis. Regardons les deux.

1. Exécuter Redis manuellement

La première consiste à exécuter Redis manuellement. Pour cela, rendez-vous sur ce lien Télécharger Redis et téléchargez le fichier « redis-x64-3.0.504.zip », puis extrayez-le sur votre machine et exécutez le fichier « redis-server.exe ».

Une fenêtre s’ouvrira et Redis fonctionnera sur votre machine tant que vous ne fermez pas la fenêtre.

2. Exécuter Redis dans un conteneur Docker

Une autre méthode consiste à exécuter Redis dans un conteneur Docker. Comme condition préalable, vous devez avoir installé Docker. Ensuite, sur la console PowerShell, exécutez la commande suivante :

docker run --name my-redis -p 6379:6379 -d redis

Si vous souhaitez vérifier si le conteneur est en cours d’exécution, vous pouvez utiliser la commande ci-dessous :

docker container ps

Comme dans l’image ci-dessous, vous pouvez vérifier l’exécution du conteneur nouvellement créé.

redis-conteneur-créé

Création de l’application et intégration avec Redis Cache

Pour utiliser le cache distribué à l’aide de Redis, créons une application API minimale ASP.NET Core, disponible dans .NET 6.

Grâce à un point de terminaison, notre application accédera à la table Orders de la base de données Northwind de RavenDB et renverra la liste de toutes les commandes. Nous aurons près de 1 000 enregistrements et nous pourrons comparer les performances en utilisant le cache Redis.

Vous pouvez accéder au code source complet du projet à ce lien : Code source.

Conditions préalables

Dans cet article, nous utiliserons RavenDB comme base de données, vous devez donc configurer le serveur RavenDB local et charger les exemples de données.

Pour ce faire, vous pouvez utiliser ce tutoriel : Configuration de RavenDB. C’est très facile à suivre.

Création de l’application

Pour créer l’application, utilisez la commande ci-dessous :

dotnet new web -o OrderManager

Ouvrez le projet avec votre IDE préféré. Dans cet exemple, j’utiliserai Visual Studio 2022.

Double-cliquez sur le projet (OrderManager.csproj) et ajoutez le code ci-dessous pour installer les dépendances. Ensuite, recompilez le projet.

  <ItemGroup>
    <PackageReference Include="RavenDB.Client" Version="5.3.1" />
  </ItemGroup>

Création des utilisations globales

Les « utilisations globales » sont des fonctionnalités disponibles sur C# 10 et permettent d’utiliser des références pour n’importe quelle classe du projet. Pour utiliser cette fonctionnalité, créez un dossier appelé « Helpers » et à l’intérieur, créez une classe appelée « GlobalUsings ». Remplacez le code généré par le code ci-dessous. Ne vous inquiétez pas des erreurs d’importation, nous créerons plus tard les espaces de noms qui n’existent pas encore.

global using OrderManager.Models;
global using OrderManager.Raven;
global using Newtonsoft.Json;
global using Raven.Client.Documents;
global using Microsoft.Extensions.Caching.Distributed;
global using System.Text;

Ensuite, créons notre entité de base de données. Alors, créez un dossier appelé « Models » et à l’intérieur, ajoutez une classe appelée « Order » et collez-y le code suivant :

namespace OrderManager.Models;

public record Order
{
    public string Company { get; set; }
    public string Employee { get; set; }
    public double Freight { get; set; }
    public List<Line> Lines { get; set; }
    public DateTime OrderedAt { get; set; }
    public DateTime RequireAt { get; set; }
    public ShipTo ShipTo { get; set; }
    public string ShipVia { get; set; }
    public object ShippedAt { get; set; }

    [JsonProperty("@metadata")]
    public Metadata Metadata { get; set; }
}

public class Line
{
    public double Discount { get; set; }
    public double PricePerUnit { get; set; }
    public string Product { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
}

public class Location
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
}

public class ShipTo
{
    public string City { get; set; }
    public string Country { get; set; }
    public string Line1 { get; set; }
    public object Line2 { get; set; }
    public Location Location { get; set; }
    public string PostalCode { get; set; }
    public string Region { get; set; }
}

public class Metadata
{
    [JsonProperty("@collection")]
    public string Collection { get; set; }

    [JsonProperty("@flags")]
    public string Flags { get; set; }
}

Configuration de la connexion avec RavenDB

Créons maintenant la configuration avec RavenDB. Créez donc un nouveau dossier nommé « Raven » et à l’intérieur, créez une nouvelle classe appelée « DocumentStoreHolder » et collez-y le code suivant :

namespace OrderManager.Raven;

public static class DocumentStoreHolder
{
    private static readonly Lazy<IDocumentStore> LazyStore = new Lazy<IDocumentStore>(() =>
    {
        IDocumentStore store = new DocumentStore
        {
            Urls = new[] { "http://localhost:8080/" },
            Database = "Northwind"
        };

        store.Initialize();

        return store;
    });

    public static IDocumentStore Store => LazyStore.Value;
}

Configuration des terminaux

La dernière partie de l’implémentation consistera à créer les points de terminaison pour consommer les données. Dans le fichier « Program.cs », remplacez le code existant par le code ci-dessous :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDistributedMemoryCache();

var app = builder.Build();

app.MapGet("/orders", () =>
{
    using (var session = DocumentStoreHolder.Store.OpenSession())
    {
        var orders = session.Query<Order>().ToList();

        return orders;
    }
});

app.MapGet("/orders/redis", async (IDistributedCache distributedCache) =>
{
    try
    {
        using (var session = DocumentStoreHolder.Store.OpenSession())
        {        
            var orders = session.Query<Order>().ToList();

            var cacheKey = "orderList";

            string serializedOrders;

            var orderList = new List<Order>();

            var redisOrders = await distributedCache.GetAsync(cacheKey);

            if (redisOrders != null)
            {
                serializedOrders = Encoding.UTF8.GetString(redisOrders);
                orderList = JsonConvert.DeserializeObject<List<Order>>(serializedOrders);
            }
            else
            {
                orderList = orders.ToList();

                serializedOrders = JsonConvert.SerializeObject(orderList);

                redisOrders = Encoding.UTF8.GetBytes(serializedOrders);

                var options = new DistributedCacheEntryOptions()
                      .SetAbsoluteExpiration(DateTime.Now.AddMinutes(10))
                      .SetSlidingExpiration(TimeSpan.FromMinutes(2));

                distributedCache.SetAsync(cacheKey, redisOrders, options);
            }
            return orderList;
        }
    }
    catch (global::System.Exception ex)
    {
        throw;
    }
});

app.Run();

Dans le point de terminaison « /orders/redis », une vérification est effectuée si les données existent dans le cache. Si tel est le cas, il est renvoyé instantanément, ce qui rend la requête très rapide. Sinon, la requête est effectuée dans la base de données, puis les données sont écrites dans le cache Redis afin que lors de la requête suivante, elles soient accessibles dans le cache.

Tester le temps de requête avec Fiddler

Dans cet exemple, Fiddler Everywhere sera utilisé pour effectuer la requête.

Dans la première image, nous faisons la demande à l’application sur le point de terminaison Redis. Dans ce cas, les données n’existent pas encore dans le cache, donc le temps de réponse était de 3,8 secondes.

Première demande - 3,8 secondes

Dans la deuxième image, les données existent déjà dans le cache, car la première requête a effectué l’enregistrement, donc la réponse a été beaucoup plus rapide (96 millisecondes).

Deuxième requête - 96 millisecondes

Conclusion

Dans cet article, nous avons vu ce qu’est un cache distribué, ses avantages dans l’utilisation de données rapides et cohérentes, et certaines fonctionnalités qui s’y rapportent avec Redis pour enregistrer les données.

De plus, nous avons créé une application pour démontrer l’utilisation du cache distribué et nous avons vérifié la rapidité des requêtes lors de son utilisation.

Plus la masse de données est importante, plus les performances sont élevées lors de l’utilisation d’un cache distribué. N’hésitez donc pas à l’utiliser dans vos applications.




Source link