Autor: Iqbal Khan
Mit einem verteilten Cache können Sie die Anwendungsleistung und Skalierbarkeit erheblich verbessern. Die Anwendungsleistung wird verbessert, da ein In-Memory-Cache für den Datenzugriff viel schneller ist als eine Datenbank. Und die Skalierbarkeit wird erreicht, indem der Cache als verteilter Cache auf mehrere Server erweitert wird und nicht nur mehr Speicherkapazität, sondern auch ein höherer Transaktionsdurchsatz pro Sekunde erzielt wird.
Trotz dieser leistungsstarken Vorteile gibt es bei vielen In-Memory-Caches ein Problem. Und das hat damit zu tun, dass die meisten Daten relational sind, während ein Cache normalerweise eine einfache Hash-Tabelle mit einem Schlüssel-Wert-Paar-Konzept ist. Jedes Element wird unabhängig im Cache gespeichert, ohne dass andere verwandte Elemente bekannt sind. Und das macht es für Anwendungen schwierig, die Beziehungen zwischen verschiedenen zwischengespeicherten Elementen zu verfolgen, sowohl um sie abzurufen als auch um die Datenintegrität zu gewährleisten, falls ein Element aktualisiert oder entfernt wird und die zugehörigen Elemente ebenfalls in der Datenbank aktualisiert oder entfernt werden. Wenn dies geschieht, weiß der Cache nichts davon und kann damit nicht umgehen.
Eine typische reale Anwendung befasst sich mit relationalen Daten, die Eins-zu-Eins-, Viele-zu-Eins-, Eins-zu-Viele- und Viele-zu-Viele-Beziehungen zu anderen Datenelementen in der Datenbank haben. Dies erfordert die Aufrechterhaltung der referenziellen Integrität über verschiedene verwandte Datenelemente hinweg. Deshalb, um Wahrung der Datenintegrität im Cache, muss der Cache diese Beziehungen verstehen und die gleiche referenzielle Integrität beibehalten.
Um diese Situationen zu bewältigen, wurde von Microsoft die Cache-Abhängigkeit im ASP.NET-Cache eingeführt. Mit der Cache-Abhängigkeit können Sie verschiedene zwischengespeicherte Elemente in Beziehung setzen. Wenn Sie dann ein zwischengespeichertes Element aktualisieren oder entfernen, entfernt der Cache automatisch alle zugehörigen zwischengespeicherten Elemente, um die Datenintegrität sicherzustellen. Wenn Ihre Anwendung diese zugehörigen Elemente dann beim nächsten Mal nicht im Cache findet, geht sie zur Datenbank, ruft die neueste Kopie dieser Elemente ab und speichert sie dann erneut im Cache, wobei die korrekte referenzielle Integrität gewahrt bleibt.
Dies ist eine großartige Funktion in ASP.NET Cache, aber ASP.NET Cache ist von Natur aus ein eigenständiger Cache, der nur für In-Process-Umgebungen mit einem Server geeignet ist. Aus Gründen der Skalierbarkeit müssen Sie jedoch a verwenden verteilter Cache Dies kann außerhalb Ihres Anwendungsprozesses erfolgen und auf mehrere Cache-Server skaliert werden. NCache ist ein solcher Cache und bietet glücklicherweise die gleiche Cache-Abhängigkeitsfunktion in einer verteilten Umgebung. Sie können Elemente auf einem physischen Cache-Server zwischengespeichert haben, abhängig von zwischengespeicherten Elementen auf einem anderen physischen Cache-Server, solange beide Teile desselben logischen Cluster-Cache sind. Und, NCache kümmert sich um alle oben genannten Fragen der Datenintegrität.
In diesem Artikel wird erläutert, wie Sie die Cache-Abhängigkeit verwenden können, um Eins-zu-eins-, Eins-zu-viele- und Viele-zu-viele-Beziehungen im Cache zu verarbeiten. Es benutzt NCache als Beispiel, aber die gleichen Konzepte gelten auch für ASP.NET Cache.
Obwohl, NCache bietet verschiedene Arten von Abhängigkeiten, einschließlich Datenabhängigkeit, Dateiabhängigkeit, SQL-Abhängigkeit und Benutzerdefinierte AbhängigkeitIn diesem Artikel wird nur die Datenabhängigkeit für die Handhabung von Beziehungen zwischen zwischengespeicherten Elementen erläutert.
Datenabhängigkeit ist eine Funktion, mit der Sie angeben können, dass ein zwischengespeichertes Element von einem anderen zwischengespeicherten Element abhängt. Wenn dann das zweite zwischengespeicherte Element jemals aktualisiert oder entfernt wird, wird auch das erste davon abhängige Element aus dem Cache entfernt. Mit der Datenabhängigkeit können Sie mehrstufige Abhängigkeiten angeben, bei denen A von B abhängt, das dann von C abhängt. Wenn C dann aktualisiert oder entfernt wird, werden sowohl A als auch B aus dem Cache entfernt.
Nachfolgend finden Sie ein kurzes Beispiel für die Verwendung der Datenabhängigkeit zur Angabe einer mehrstufigen Abhängigkeit.
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;
}
}
Das folgende Beispiel wird in diesem Artikel verwendet, um zu demonstrieren, wie verschiedene Arten von Beziehungen im Cache gehandhabt werden.
Im obigen Diagramm werden die folgenden Beziehungen dargestellt:
Für die oben genannten Beziehungen werden die folgenden Domänenobjekte entworfen.
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;
}
Wie Sie sehen können, enthalten die Klassen „Customer“ und „Product“ eine _Bestellliste um eine Liste aller Auftragsobjekte zu enthalten, die sich auf diesen Kunden beziehen. Ebenso enthält die Order-Klasse _Kunde und _Produkt Datenelemente, die auf das zugehörige Kunden- oder Produktobjekt verweisen. Nun ist es die Aufgabe des Persistenzcodes, diese Objekte aus der Datenbank zu laden, um sicherzustellen, dass beim Laden eines Kunden auch alle seine Bestellobjekte geladen werden.
Im Folgenden zeige ich, wie jede dieser Beziehungen im Cache gehandhabt wird.
Immer wenn Sie ein Objekt aus dem Cache abgerufen haben, das ebenfalls eine Eins-zu-Eins- oder Viele-zu-Eins-Beziehung zu einem anderen Objekt hat, hat Ihr Persistenzcode möglicherweise auch das zugehörige Objekt geladen. Es ist jedoch nicht immer erforderlich, das zugehörige Objekt zu laden, da die Anwendung es zu diesem Zeitpunkt möglicherweise nicht benötigt. Wenn Ihr Persistenzcode das zugehörige Objekt geladen hat, müssen Sie es verarbeiten.
Es gibt zwei Möglichkeiten, wie Sie damit umgehen können. Ich werde den einen als optimistisch und den anderen als pessimistisch bezeichnen und jeden von ihnen im Folgenden erläutern:
Nachfolgend finden Sie den Quellcode für den Umgang mit der optimistischen Situation. Bitte beachten Sie, dass sowohl das Primärobjekt als auch seine beiden zugehörigen Objekte als ein Element zwischengespeichert werden, da die Serialisierung des Primärobjekts auch die zugehörigen Objekte umfassen würde.
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();
}
Nachfolgend finden Sie den Quellcode für den Umgang mit der pessimistischen Situation, da das optimistische Szenario keine Verwendung von Datenabhängigkeit erfordert.
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();
}
Der obige Code lädt ein Order-Objekt aus der Datenbank und sowohl Customer- als auch Product-Objekte werden automatisch damit geladen, da das Order-Objekt eine n:XNUMX-Beziehung zu ihnen hat. Die Anwendung fügt dann Kunden- und Produktobjekte zum Cache hinzu und fügt dann das Bestellobjekt zum Cache hinzu, jedoch mit einer Abhängigkeit sowohl von Kunden- als auch von Produktobjekten. Wenn eines dieser Kunden- oder Produktobjekte im Cache aktualisiert oder entfernt wird, wird das Bestellobjekt automatisch aus dem Cache entfernt, um die Datenintegrität zu wahren. Die Anwendung muss diese Beziehung nicht verfolgen.
Immer wenn Sie ein Objekt aus dem Cache abgerufen haben, das ebenfalls eine Eins-zu-viele-Beziehung mit einem anderen Objekt hat, lädt Ihr Persistenzcode möglicherweise sowohl das Primärobjekt als auch eine Sammlung aller seiner Eins-zu-viele-bezogenen Objekte. Es ist jedoch nicht immer erforderlich, die zugehörigen Objekte zu laden, da die Anwendung sie zu diesem Zeitpunkt möglicherweise nicht benötigt. Wenn Ihr Persistenzcode die zugehörigen Objekte geladen hat, müssen Sie sie im Cache verarbeiten. Bitte beachten Sie, dass die zugehörigen Objekte alle in einer Sammlung aufbewahrt werden und dies eigene Probleme mit sich bringt, die im Folgenden besprochen werden.
Es gibt drei Möglichkeiten, wie Sie damit umgehen können. Ich werde einen optimistischen, einen leicht pessimistischen und einen wirklich pessimistischen Weg nennen und jeden von ihnen im Folgenden erklären:
Nachfolgend finden Sie ein Beispiel dafür, wie Sie optimistisch mit Eins-zu-vielen-Beziehungen umgehen können. Bitte beachten Sie, dass die Sammlung mit den zugehörigen Objekten beim Einfügen in den Cache als Teil des Primärobjekts serialisiert wird.
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();
}
Nachfolgend finden Sie ein Beispiel dafür, wie man mit einer Eins-zu-Viele-Beziehung leicht pessimistisch umgeht.
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();
}
Im obigen Beispiel die Liste der Order-Objekte, die damit in Zusammenhang stehen Kundenfälle wird separat zwischengespeichert. Die gesamte Sammlung wird als ein Element zwischengespeichert, da wir davon ausgehen, dass niemand einzelne Order-Objekte direkt separat ändern wird. Die Anwendung ruft es immer über diesen Kunden ab und ändert die gesamte Sammlung erneut und speichert sie erneut zwischen.
Ein anderer Fall ist der pessimistische Umgang mit Eins-zu-vielen-Beziehungen, der dem Umgang mit Sammlungen im Cache ähnelt. Dieses Thema wird im nächsten Abschnitt behandelt.
Es gibt viele Situationen, in denen Sie eine Sammlung von Objekten aus der Datenbank abrufen. Dies könnte auf eine von Ihnen ausgeführte Abfrage zurückzuführen sein oder es könnte sich um eine Eins-zu-Viele-Beziehung handeln, die eine Sammlung verwandter Objekte auf der „Viele“-Seite zurückgibt. In jedem Fall erhalten Sie eine Sammlung von Objekten, die im Cache entsprechend behandelt werden müssen.
Es gibt zwei Möglichkeiten, Sammlungen zu verwalten, wie unten erläutert:
Nachfolgend finden Sie ein Beispiel für den optimistischen Umgang mit Sammlungen.
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();
}
Im obigen Beispiel wird die gesamte Sammlung als ein Element zwischengespeichert und alle in der Sammlung enthaltenen Kundenobjekte werden automatisch zusammen mit der Sammlung und dem Cache serialisiert. Daher besteht hier keine Notwendigkeit, eine Datenabhängigkeit zu erstellen.
Nachfolgend finden Sie ein Beispiel für den pessimistischen Umgang mit Sammlungen.
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();
}
Im obigen Beispiel wird jedes Objekt in der Sammlung als separates Element zwischengespeichert, und dann wird die gesamte Sammlung sowie ein Element zwischengespeichert. Die Sammlung weist eine Datenabhängigkeit von allen Objekten auf, die separat zwischengespeichert werden. Wenn eines dieser Objekte aktualisiert oder entfernt wird, wird die Sammlung auf diese Weise auch aus dem Cache entfernt.
Autor: Iqbal Khan arbeitet für Alachisoft , ein führendes Softwareunternehmen, das Lösungen für verteiltes .NET- und Java-Caching, O/R-Mapping und SharePoint-Speicheroptimierung anbietet. Sie erreichen ihn unter iqbal@alachisoft.com €XNUMX.