Verwalten von Datenbeziehungen im verteilten Cache

Autor: Iqbal Khan

Einleitung

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.

Was ist Datenabhängigkeit im Cache?

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;
    }
}

Mehrstufige Datenabhängigkeit


Datenbeziehungen

Das folgende Beispiel wird in diesem Artikel verwendet, um zu demonstrieren, wie verschiedene Arten von Beziehungen im Cache gehandhabt werden.

Verwalten von Datenbeziehungen
Abbildung 2: Beziehungen in der Datenbank

Im obigen Diagramm werden die folgenden Beziehungen dargestellt:

  • Einer zu vielen: Es gibt zwei solcher Beziehungen und sie sind:
    1. Kunde zur Bestellung
    2. Produkt auf Bestellung
  • Viele zu eins: Es gibt zwei solcher Beziehungen und sie sind:
    1. Bestellung an den Kunden
    2. Bestellung zum Produkt
  • Viel zu viel: Es gibt eine solche Beziehung, und zwar:
    1. Vom Kunden zum Produkt (per Bestellung)

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.

Umgang mit Eins-zu-Eins-/Viele-zu-Eins-Beziehungen

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:

  1. Optimistischer Umgang mit Beziehungen: Dabei gehen wir davon aus, dass, obwohl Beziehungen bestehen, niemand das zugehörige Objekt separat ändern wird. Wer die zugehörigen Objekte ändern möchte, ruft diese über das primäre Objekt im Cache ab und ist daher in der Lage, sowohl primäre als auch verwandte Objekte zu ändern. In diesem Fall müssen wir diese beiden Objekte nicht separat im Cache speichern. Daher enthält das Primärobjekt das zugehörige Objekt und beide werden als ein zwischengespeichertes Element im Cache gespeichert. Und es entsteht keine Datenabhängigkeit zwischen ihnen.
  2. Pessimistischer Umgang mit Beziehungen: In diesem Fall gehen Sie davon aus, dass das zugehörige Objekt unabhängig von einem anderen Benutzer abgerufen und aktualisiert werden kann und daher das zugehörige Objekt als separates zwischengespeichertes Element gespeichert werden muss. Wenn dann jemand das zugehörige Objekt aktualisiert oder entfernt, soll auch Ihr primäres Objekt aus dem Cache entfernt werden. In diesem Fall erstellen Sie eine Datenabhängigkeit zwischen den beiden Objekten.

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();
}

Optimistischer Umgang mit Viele-zu-Eins-Beziehungen

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();
}

Pessimistischer Umgang mit Viele-zu-Eins-Beziehungen

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.

Umgang mit Eins-zu-Viele-Beziehungen

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:

  1. Optimistischer Umgang mit Beziehungen: Dabei gehen wir davon aus, dass, obwohl Beziehungen bestehen, niemand sonst die zugehörigen Objekte separat ändern wird. Wer die zugehörigen Objekte ändern möchte, ruft sie über das Primärobjekt im Cache ab und ist daher in der Lage, sowohl Primär- als auch verwandte Objekte zu ändern. In diesem Fall müssen wir diese beiden Objekte nicht separat im Cache speichern. Daher enthält das Primärobjekt das zugehörige Objekt und beide werden als ein zwischengespeichertes Element im Cache gespeichert. Und es entsteht keine Datenabhängigkeit zwischen ihnen.
  2. Leicht pessimistischer Umgang mit Beziehungen: In diesem Fall gehen Sie davon aus, dass die zugehörigen Objekte unabhängig voneinander abgerufen werden können, jedoch nur als gesamte Sammlung und niemals als einzelne Objekte. Daher speichern Sie die Sammlung als ein zwischengespeichertes Element und erstellen eine Abhängigkeit von der Sammlung zum Primärobjekt. Wenn dann jemand das Primärobjekt aktualisiert oder entfernt, möchten Sie, dass Ihre Sammlung auch aus dem Cache entfernt wird.
  3. Wirklich pessimistischer Umgang mit Beziehungen: In diesem Fall gehen Sie davon aus, dass alle Objekte in der zugehörigen Sammlung auch einzeln von der Anwendung abgerufen und geändert werden können. Daher müssen Sie nicht nur die Sammlung, sondern auch alle ihre einzelnen Objekte separat im Cache speichern. Bitte beachten Sie jedoch, dass dies wahrscheinlich zu Leistungsproblemen führen würde, da Sie mehrmals auf den Cache zugreifen, der sich möglicherweise im gesamten Netzwerk auf einem Cache-Server befindet. Ich werde dies im nächsten Abschnitt besprechen, der sich mit „Umgang mit Sammlungen im Cache“ befasst.

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();
}

Optimistischer Umgang mit Eins-zu-Viele-Beziehungen


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();
}

Umgang mit Eins-zu-vielen-Beziehungen leicht pessimistisch

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.

Umgang mit Sammlungen im Cache

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:

  1. Optimistischer Umgang mit Inkasso: Dabei gehen wir davon aus, dass die gesamte Sammlung als ein Element zwischengespeichert werden sollte, da niemand die in der Sammlung enthaltenen Objekte einzeln abrufen und ändern wird. Die Sammlung wird möglicherweise für einen kurzen Zeitraum zwischengespeichert und diese Annahme ist möglicherweise durchaus berechtigt.
  2. Pessimistischer Umgang mit Sammlungen: In diesem Fall gehen wir davon aus, dass einzelne Objekte innerhalb der Sammlung separat abgerufen und geändert werden können. Deshalb zwischenspeichern wir die gesamte Sammlung, aber dann auch jedes einzelne Objekt und erstellen eine Abhängigkeit von der Sammlung zu den einzelnen Objekten.

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();
}

Optimistischer Umgang mit Sammlungen

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();
}

Pessimistischer Umgang mit Sammlungen

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.

© Copyright Alachisoft 2002 - Alle Rechte vorbehalten NCache ist eine eingetragene Marke der Diyatech Corp.