Auteur : Iqbal Khan
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.
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;
}
}
L'exemple suivant est utilisé dans cet article pour montrer comment différents types de relations sont gérés dans le cache.
Dans le diagramme ci-dessus, les relations suivantes sont indiquées :
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.
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 :
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();
}
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();
}
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.
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 :
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();
}
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();
}
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.
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 :
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();
}
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();
}
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.