Gestion des relations de données dans le cache distribué

Auteur : Iqbal Khan

Introduction

Un cache distribué vous permet d'améliorer considérablement les performances et l'évolutivité des applications. Les performances des applications sont améliorées car un cache en mémoire est beaucoup plus rapide pour l'accès aux données qu'une base de données. De plus, l'évolutivité est obtenue en développant le cache sur plusieurs serveurs en tant que cache distribué et en obtenant non seulement plus de capacité de stockage, mais également plus de transactions par seconde.

Malgré ces avantages puissants, de nombreux caches en mémoire sont confrontés à un problème. Et cela a à voir avec le fait que la plupart des données sont relationnelles alors qu'un cache est généralement une simple table de hachage avec un concept de paire clé-valeur. Chaque élément est stocké dans le cache indépendamment sans aucune connaissance d'autres éléments connexes. Et cela rend difficile pour les applications de suivre les relations entre les différents éléments mis en cache à la fois pour les récupérer et également pour l'intégrité des données dans le cas où un élément est mis à jour ou supprimé et que ses éléments associés sont également mis à jour ou supprimés dans la base de données. Lorsque cela se produit, le cache ne le sait pas et ne peut pas le gérer.

Une application réelle typique traite des données relationnelles qui ont des relations un à un, plusieurs à un, un à plusieurs et plusieurs à plusieurs avec d'autres éléments de données dans la base de données. Cela nécessite que l'intégrité référentielle soit maintenue à travers différents éléments de données connexes. Par conséquent, afin de préserver l'intégrité des données dans le cache, le cache doit comprendre ces relations et conserver la même intégrité référentielle.

Pour gérer ces situations, la dépendance au cache a été introduite par Microsoft dans le cache ASP.NET. La dépendance du cache vous permet de relier divers éléments mis en cache, puis chaque fois que vous mettez à jour ou supprimez un élément mis en cache, le cache supprime automatiquement tous ses éléments mis en cache associés pour garantir l'intégrité des données. Ensuite, lorsque votre application ne trouve pas ces éléments associés dans le cache la prochaine fois qu'elle en a besoin, l'application accède à la base de données et récupère la dernière copie de ces éléments, puis les remet en cache en conservant une intégrité référentielle correcte.

Il s'agit d'une fonctionnalité intéressante dans le cache ASP.NET, mais le cache ASP.NET est, de par sa conception, un cache autonome qui convient uniquement aux environnements en processus à serveur unique. Mais, pour l'évolutivité, vous devez utiliser un cache distribué qui peuvent vivre en dehors de votre processus d'application et peuvent évoluer vers plusieurs serveurs de cache. NCache est un tel cache et fournit heureusement la même fonctionnalité de dépendance du cache dans un environnement distribué. Vous pouvez avoir des éléments mis en cache dans un serveur de cache physique en fonction des éléments mis en cache dans un autre serveur de cache physique tant qu'ils font tous deux partie du même cache logique en cluster. Et, NCache prend en charge tous les problèmes d'intégrité des données mentionnés ci-dessus.

Cet article explique comment vous pouvez utiliser la dépendance du cache pour gérer les relations un-à-un, un-à-plusieurs et plusieurs-à-plusieurs dans le cache. Il utilise NCache à titre d'exemple, mais les mêmes concepts s'appliquent au cache ASP.NET.

Bien que, NCache fournit différents types de dépendances, y compris Dépendance des données, Dépendance de fichier, Dépendance SQLet Dépendance personnalisée, cet article traite uniquement de la dépendance des données pour la gestion des relations entre les éléments mis en cache.

Qu'est-ce que la dépendance des données dans le cache ?

La dépendance des données est une fonctionnalité qui vous permet de spécifier qu'un élément mis en cache dépend d'un autre élément mis en cache. Ensuite, si le deuxième élément mis en cache est mis à jour ou supprimé, le premier élément qui en dépendait est également supprimé du cache. La dépendance des données vous permet de spécifier des dépendances à plusieurs niveaux où A dépend de B, qui dépend ensuite de C. Ensuite, si C est mis à jour ou supprimé, A et B sont supprimés du cache.

Vous trouverez ci-dessous un bref exemple d'utilisation de la dépendance des données pour spécifier une dépendance à plusieurs niveaux.

public static void CreateDependencies(ICache _cache)
{
    try
    {
        string keyC = "objectC-1000";
        Object objC = new Object();
        string keyB = "objectB-1000";
        Object objB = new Object();
        string keyA = "objectA-1000";
        Object objA = new Object();
        // Initializing cacheItems
        var itemOne = new CacheItem(objA);
        var itemTwo = new CacheItem(objB);
        var itemThree = new CacheItem(objC);
        // Adding objA dependent on ObjB
        itemOne.Dependency = new KeyDependency(keyB);
        itemTwo.Dependency = new KeyDependency(keyC);
        //Adding items to cache
        _cache.Add(keyC, itemThree);
        _cache.Add(keyB, itemTwo);
        _cache.Add(keyA, itemOne);

        // Removing "objC" automatically removes “objB” as well as "ObjA"
        _cache.Remove(keyC);
        _cache.Dispose();
    }
    catch (Exception e)
    {
        throw;
    }
}

Dépendance des données à plusieurs niveaux


Relations de données

L'exemple suivant est utilisé dans cet article pour montrer comment différents types de relations sont gérés dans le cache.

Gestion des relations de données
Figure 2 : Relations dans la base de données

Dans le diagramme ci-dessus, les relations suivantes sont indiquées :

  • Un à plusieurs : Il existe deux relations de ce type et elles sont :
    1. Client à commander
    2. Produit à commander
  • Plusieurs à un : Il existe deux relations de ce type et elles sont :
    1. Commande au client
    2. Commande au produit
  • Plusieurs à plusieurs: Il existe une telle relation et c'est:
    1. Client à produit (via la commande)

Pour les relations ci-dessus, les objets de domaine suivants sont conçus.

class Customer
    {
        public string CustomerID;
        public string CompanyName;
        public string ContactName;
        public string ContactTitle;
        public string Phone;
        public string Country;
        public IList<Order> _OrderList;
    }
    class Product
    {
        public int ProductID;
        public string ProductName;
        public Decimal UnitPrice;
        public int UnitsInStock;
        public int UnitsOnOrder;
        public int ReorderLevel;
  
        public IList<Order> _OrderList;
    }
    class Order
    {
        public int OrderId;
        public string CustomerID;
        public DateTime OrderDate;
        public DateTime RequiredDate;
        public DateTime ShippedDate;
        public int ProductID;
        public Decimal UnitPrice;
        public int Quantity;
        public Single Discount;
        public Customer _Customer;
        public Product _Product;
    }

Comme vous pouvez le voir, les classes Customer et Product contiennent un _ListeCommande pour contenir une liste de tous les objets Commande liés à ce client. De même, la classe Order contient _Client ainsi que _Produit membres de données pour pointer vers l'objet client ou produit associé. Désormais, c'est le travail du code de persistance qui charge ces objets à partir de la base de données pour s'assurer que chaque fois qu'un client est chargé, tous ses objets de commande sont également chargés.

Ci-dessous, je vais montrer comment chacune de ces relations est gérée dans le cache.

Gestion des relations un à un/plusieurs à un

Chaque fois que vous avez récupéré un objet du cache qui a également une relation un-à-un ou plusieurs-à-un avec un autre objet, votre code de persistance peut également avoir chargé l'objet associé. Cependant, il n'est pas toujours nécessaire de charger l'objet associé car l'application peut ne pas en avoir besoin à ce moment-là. Si votre code de persistance a chargé l'objet associé, vous devez le gérer.

Il y a deux façons de gérer cela. J'appellerai l'un optimiste et l'autre pessimiste et expliquerai chacun d'eux ci-dessous :

  1. Gestion optimiste des relations : En cela, nous supposons que même s'il existe des relations, personne d'autre ne modifiera l'objet associé séparément. Quiconque souhaite modifier les objets liés les récupérera via l'objet primaire dans le cache et sera donc en mesure de modifier à la fois les objets primaires et les objets liés. Dans ce cas, nous n'avons pas à stocker ces deux objets séparément dans le cache. Par conséquent, l'objet principal contient l'objet associé et les deux sont stockés sous la forme d'un élément mis en cache dans le cache. Et, aucune dépendance de données n'est créée entre eux.
  2. Gestion pessimiste des relations : Dans ce cas, vous partez du principe que l'objet associé peut être extrait et mis à jour indépendamment par un autre utilisateur et que, par conséquent, l'objet associé doit être stocké en tant qu'élément distinct mis en cache. Ensuite, si quelqu'un met à jour ou supprime l'objet associé, vous souhaitez que votre objet principal soit également supprimé du cache. Dans ce cas, vous allez créer une dépendance de données entre les deux objets.

Vous trouverez ci-dessous le code source permettant de gérer la situation optimiste. Veuillez noter que l'objet principal et ses deux objets associés sont mis en cache en tant qu'un seul élément, car la sérialisation de l'objet principal inclurait également les objets associés.

static void Main(string[] args)
{
    string cacheName = "myReplicatedCache";
    ICache _cache = CacheManager.GetCache(cacheName);
    OrderFactory oFactory = new OrderFactory();
    Order order = new Order();
    order.OrderId = 1000;
    oFactory.LoadFromDb(order);
    Customer cust = order._Customer;
    Product prod = order._Product;
    var itemOne = new CacheItem(order);
    // please note that Order object serialization will
    // also include Customer and Product objects
    _cache.Add(order.OrderId.ToString(), itemOne);
    _cache.Dispose();
}

Gestion optimiste de la relation plusieurs-à-un

Vous trouverez ci-dessous le code source permettant de gérer la situation pessimiste, car le scénario optimiste ne nécessite aucune utilisation de la dépendance des données.

static void Main(string[] args)
{
    string cacheName = "myReplicatedCache";
    ICache _cache = CacheManager.GetCache(cacheName);
    OrderFactory oFactory = new OrderFactory();
    Order order = new Order();
    order.OrderId = 1000;
    oFactory.LoadFromDb(order);
    Customer cust = order._Customer;
    Product prod = order._Product;
    string custKey = "Customer:CustomerID:" + cust.CustomerID;
    _cache.Insert(custKey, cust);
    string prodKey = "Product:ProductID:" + prod.ProductID;
    _cache.Insert(prodKey, prod);
    string[] depKeys = { prodKey, custKey };
    string orderKey = "Order:OrderID:" + order.OrderId;
    // We are setting _Customer and _Product to null so they
    // don't get serialized with Order object
    order._Customer = null;
    order._Product = null;
    var item = new CacheItem(order);
    item.Dependency = new CacheDependency(null, depKeys);
    _cache.Add(orderKey, item);
    _cache.Dispose();
}

Gestion pessimiste des relations plusieurs-à-un

Le code ci-dessus charge un objet Order à partir de la base de données et les objets Customer et Product sont automatiquement chargés avec lui car l'objet Order a une relation plusieurs-à-un avec eux. L'application ajoute ensuite les objets Client et Produit au cache, puis ajoute l'objet Commande au cache, mais avec une dépendance à la fois sur les objets Client et Produit. Ainsi, si l'un de ces objets Client ou Produit est mis à jour ou supprimé du cache, l'objet Commande est automatiquement supprimé du cache pour préserver l'intégrité des données. L'application n'a pas à suivre cette relation.

Gestion des relations un-à-plusieurs

Chaque fois que vous avez récupéré un objet du cache qui a également une relation un-à-plusieurs avec un autre objet, votre code de persistance peut charger à la fois l'objet principal et une collection de tous ses objets liés un-à-plusieurs. Cependant, il n'est pas toujours nécessaire de charger les objets associés car l'application peut ne pas en avoir besoin à ce stade. Si votre code de persistance a chargé les objets associés, vous devez les gérer dans le cache. Veuillez noter que les objets associés sont tous conservés dans une seule collection, ce qui introduit des problèmes qui sont abordés ci-dessous.

Il y a trois façons de gérer cela. J'appellerai une manière optimiste, une légèrement pessimiste et une manière vraiment pessimiste et j'expliquerai chacune d'elles ci-dessous :

  1. Gestion optimiste des relations : En cela, nous supposons que même s'il existe des relations, personne d'autre ne modifiera les objets associés séparément. Quiconque souhaite modifier les objets associés les récupérera via l'objet principal dans le cache et sera donc en mesure de modifier à la fois les objets principaux et associés. Dans ce cas, nous n'avons pas à stocker ces deux objets séparément dans le cache. Par conséquent, l'objet principal contient l'objet associé et les deux sont stockés sous la forme d'un élément mis en cache dans le cache. Et, aucune dépendance de données n'est créée entre eux.
  2. Gestion légèrement pessimiste des relations : Dans ce cas, vous partez du principe que les objets associés peuvent être extraits indépendamment, mais uniquement en tant que collection entière et jamais en tant qu'objets individuels. Par conséquent, vous stockez la collection sous la forme d'un élément mis en cache et créez une dépendance entre la collection et l'objet principal. Ensuite, si quelqu'un met à jour ou supprime l'objet principal, vous souhaitez que votre collection soit également supprimée du cache.
  3. Gestion vraiment pessimiste des relations : Dans ce cas, vous partez du principe que tous les objets de la collection associée peuvent également être récupérés individuellement par l'application et modifiés. Par conséquent, vous devez non seulement stocker la collection, mais également tous leurs objets individuels dans le cache séparément. Veuillez noter cependant que cela entraînerait probablement des problèmes de performances car vous effectuez plusieurs allers-retours vers le cache qui peut résider sur le réseau sur un serveur de cache. J'en discuterai dans la section suivante qui traite de "Gestion des collections dans le cache".

Vous trouverez ci-dessous un exemple de la façon dont vous pouvez gérer les relations un-à-plusieurs de manière optimiste. Veuillez noter que la collection contenant les objets associés est sérialisée dans le cadre de l'objet principal lors de sa mise en cache.

static void Main(string[] args)
{
    string cacheName = "ltq";
    ICache _cache = CacheManager.GetCache(cacheName);
    CustomerFactory cFactory = new CustomerFactory();
    Customer cust = new Customer();
    cust.CustomerID = "ALFKI";
    cFactory.LoadFromDb(cust);
    // please note that _OrderList will automatically get
    // serialized along with the Customer object
    string custKey = "Customer:CustomerID:" + cust.CustomerID;
    _cache.Add(custKey, cust);
    _cache.Dispose();
}

Gérer la relation un-à-plusieurs avec optimisme


Vous trouverez ci-dessous un exemple de la façon de gérer une relation un à plusieurs de manière légèrement pessimiste.

static void Main(string[] args)
{
    string cacheName = "myReplicatedCache";
    ICache _cache = CacheManager.GetCache(cacheName);
    CustomerFactory cFactory = new CustomerFactory();
    Customer cust = new Customer();
    cust.CustomerID = "ALFKI";
    cFactory.LoadFromDb(cust);
    IList<Order> orderList = cust._OrderList;
    // please note that _OrderList will not be get
    // serialized along with the Customer object
    cust._OrderList = null;
    string custKey = "Customer:CustomerID:" + cust.CustomerID;
    var custItem = new CacheItem(cust);
    _cache.Add(custKey, custItem);
    // let's reset the _OrderList back
    cust._OrderList = orderList;
    string[] depKeys = { custKey };
    string orderListKey = "Customer:OrderList:CustomerId" + cust.CustomerID;
    IDictionary<string, CacheItem> dictionary = new Dictionary<string, CacheItem>();
    foreach (var order in orderList)
    {
        var orderItem = new CacheItem(order);
        orderItem.Dependency = new CacheDependency(null, depKeys);
        dictionary.Add(orderListKey, orderItem);

    }
    _cache.AddBulk(dictionary);
    _cache.Dispose();
}

Gérer les relations un à plusieurs avec un peu de pessimisme

Dans l'exemple ci-dessus, la liste des objets Order qui sont liés à ce Témoignages est mis en cache séparément. La collection entière est mise en cache en tant qu'élément unique car nous supposons que personne ne modifiera directement les objets Order individuels séparément. L'application le récupère toujours via ce client et modifie et remet en cache l'intégralité de la collection.

Un autre cas est la gestion pessimiste des relations un-à-plusieurs, qui est similaire à la façon dont nous gérons les collections dans le cache. Ce sujet est abordé dans la section suivante.

Gestion des collections dans le cache

Il existe de nombreuses situations dans lesquelles vous récupérez une collection d'objets de la base de données. Cela peut être dû à une requête que vous avez exécutée ou à une relation un-à-plusieurs renvoyant une collection d'objets associés du côté "plusieurs". Dans tous les cas, vous obtenez une collection d'objets qui doivent être gérés correctement dans le cache.

Il existe deux manières de gérer les collections, comme expliqué ci-dessous :

  1. Gestion optimale des collections : Dans ce cas, nous supposons que la collection entière doit être mise en cache en tant qu'élément unique, car personne ne récupérera et ne modifiera individuellement les objets conservés dans la collection. La collection peut être mise en cache pendant une brève période et cette hypothèse peut être tout à fait valable.
  2. Gestion pessimiste des collections : Dans ce cas, nous supposons que les objets individuels à l'intérieur de la collection peuvent être récupérés séparément et modifiés. Par conséquent, nous mettons en cache la collection entière, mais nous mettons également en cache chaque objet individuel et créons une dépendance de la collection aux objets individuels.

Vous trouverez ci-dessous un exemple de la façon de gérer les collections de manière optimiste.

static void Main(string[] args)
{
    string cacheName = "myReplicatedCache";
    ICache _cache = CacheManager.GetCache(cacheName);
    CustomerFactory cFactory = new CustomerFactory();
    Customer cust = new Customer();
    string custListKey = "CustomerList:LoadByCountry:Country:United States";
    IList<Customer> custList = cFactory.LoadByCountry("United States");
    IDistributedList<Customer> list = _cache.DataTypeManager.CreateList<Customer>(custListKey);

    // please note that all Customer objects kept in custList
    // will be serialized along with the custList
    foreach (var customer in custList)
    {
        // Add products to list
        list.Add(customer);
    }
    _cache.Dispose();
}

Gérer les collections avec optimisme

Dans l'exemple ci-dessus, la collection entière est mise en cache comme un seul élément et tous les objets Customer conservés dans la collection sont automatiquement sérialisés avec la collection et le cache. Par conséquent, il n'est pas nécessaire de créer une dépendance de données ici.

Vous trouverez ci-dessous un exemple de la façon de gérer les collections de manière pessimiste.

static void Main(string[] args)
{
    string cacheName = "myReplicatedCache";
    ICache _cache = CacheManager.GetCache(cacheName);
    CustomerFactory cFactory = new CustomerFactory();
    Customer cust = new Customer();
    IList<Customer> custList = cFactory.LoadByCountry("United States");
    ArrayList custKeys = new ArrayList();
    // Let's cache individual Customer objects and also build
    // an array of keys to be used later in CacheDependency
    foreach (Customer c in custList)
    {
        string custKey = "Customer:CustomerID:" + c.CustomerID;
        custKeys.Add(custKey);
        _cache.Insert(custKey, c);
    }
    string custListKey = "CustomerList:LoadByCountry:Country:United States";
    // please note that this collection has a dependency on all
    // objects in it separately. So, if any of them are updated or
    // removed, this collection will also be removed from cache
    IDistributedList<Customer> list = _cache.DataTypeManager.CreateList<Customer>(custListKey);
    foreach (var customer in custList)
    {
        // Add products to list
        var item = new CacheItem(customer);
        item.Dependency = new CacheDependency(null, (string[])custKeys.ToArray());
        list.Add(customer);
    }

    _cache.Dispose();
}

Gérer les collections avec pessimisme

Dans l'exemple ci-dessus, chaque objet de la collection est mis en cache en tant qu'élément distinct, puis la collection entière est mise en cache ainsi qu'un élément. La collection a une dépendance de données sur tous ses objets qui sont mis en cache séparément. Ainsi, si l'un de ces objets est mis à jour ou supprimé, la collection est également supprimée du cache.


Auteur : Iqbal Khan travaillant pour Alachisoft , un éditeur de logiciels de premier plan fournissant des solutions de mise en cache distribuée .NET et Java, de mappage O/R et d'optimisation du stockage SharePoint. Vous pouvez le joindre au iqbal@alachisoft.com.

© Copyright Alachisoft 2002 - . Tous droits réservés. NCache est une marque déposée de Diyatech Corp.