管理分布式缓存中的数据关系

作者:伊克巴尔汗

介绍

分布式缓存可让您极大地提高应用程序性能和可扩展性。 应用程序性能得到了提高,因为内存中缓存的数据访问速度比数据库快得多。 而且,可扩展性是通过将缓存增加到多个服务器作为分布式缓存来实现的,不仅可以获得更多的存储容量,还可以获得更多的每秒事务吞吐量。

尽管有如此强大的优势,但许多内存缓存仍面临一个问题。 这与大多数数据是关系的事实有关,而缓存通常是具有键值对概念的简单哈希表。 每个项目都独立存储在缓存中,而无需了解任何其他相关项目。 并且,这使得应用程序很难跟踪不同缓存项之间的关系,以便在获取它们以及在更新或删除一个项目并且其相关项目也在数据库中更新或删除的情况下保持数据完整性。 发生这种情况时,缓存不知道它并且无法处理它。

典型的现实应用程序处理与数据库中的其他数据元素具有一对一、多对一、一对多和多对多关系的关系数据。 这需要在不同的相关数据元素之间保持参照完整性。 因此,为了 保持缓存中的数据完整性,缓存必须理解这些关系并保持相同的参照完整性。

为了处理这些情况,微软在 ASP.NET Cache 中引入了 Cache Dependency。 Cache Dependency 允许您关联各种缓存元素,然后每当您更新或删除任何缓存项时,缓存会自动删除所有相关的缓存项以确保数据完整性。 然后,当您的应用程序在下次需要它们时在缓存中找不到这些相关项时,应用程序会转到数据库并获取这些项的最新副本,然后再次缓存它们并保持正确的引用完整性。

这是 ASP.NET 缓存中的一个很棒的功能,但 ASP.NET 缓存设计为一个独立缓存,仅适用于单服务器进程内环境。 但是,为了可扩展性,您必须使用 分布式缓存 它可以存在于您的应用程序进程之外,并且可以扩展到多个缓存服务器。 NCache 就是这样一个缓存,幸运的是在分布式环境中提供了相同的缓存依赖特性。 您可以在一个物理缓存服务器中缓存项目,这取决于另一个物理缓存服务器中的缓存项目,只要它们都是同一逻辑集群缓存的一部分。 和, NCache 处理上述所有数据完整性问题。

本文介绍如何使用 Cache Dependency 处理缓存中的一对一、一对多和多对多关系。 它用 NCache 作为示例,但相同的概念适用于 ASP.NET 缓存。

虽然, NCache 提供各种类型的依赖项,包括 数据依赖, 文件依赖, SQL 依赖自定义依赖,本文只讨论处理缓存项之间关系的数据依赖关系。

什么是缓存中的数据依赖关系?

数据依赖性是一项功能,可让您指定一个缓存项依赖于另一个缓存项。 然后,如果第二个缓存项被更新或删除,则依赖于它的第一个项也会从缓存中删除。 Data Dependency 允许您指定多级依赖关系,其中 A 依赖于 B,然后 B 依赖于 C。然后,如果 C 被更新或删除,则 A 和 B 都将从缓存中删除。

下面是一个简短的例子,说明如何使用数据依赖来指定多级依赖。

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

多级数据依赖


数据关系

本文使用以下示例来演示如何在缓存中处理各种类型的关系。

管理数据关系
图 2:数据库中的关系

在上图中,显示了以下关系:

  • 一对多: 有两种这样的关系,它们是:
    1. 客户订购
    2. 订购产品
  • 多对一: 有两种这样的关系,它们是:
    1. 订单给客户
    2. 订单到产品
  • 多对多: 有一种这样的关系,那就是:
    1. 客户到产品(通过订单)

针对上述关系,设计了以下领域对象。

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

如您所见,Customer 和 Product 类包含一个 _订单 包含与此客户相关的所有订单对象的列表。 同样,Order 类包含 _顾客_产品 数据成员指向相关的客户或产品对象。 现在,持久性代码的工作是从数据库中加载这些对象,以确保每当加载 Customer 时,也加载其所有 Order 对象。

下面,我将展示如何在缓存中处理这些关系。

处理一对一/多对一关系

每当您从缓存中获取与另一个对象也具有一对一或多对一关系的对象时,您的持久性代码也可能已经加载了相关对象。 但是,并不总是需要加载相关对象,因为当时应用程序可能不需要它。 如果您的持久性代码已加载相关对象,那么您需要处理它。

有两种方法可以处理这个问题。 我将称一种乐观方式和另一种悲观方式,并将在下面解释它们中的每一个:

  1. 乐观处理关系: 在此,我们假设即使存在关系,其他人也不会单独修改相关对象。 任何想要修改相关对象的人都将通过缓存中的主对象获取它,因此可以同时修改主对象和相关对象。 在这种情况下,我们不必将这两个对象分别存储在缓存中。 因此,主对象包含相关对象,并且它们都作为一个缓存项存储在缓存中。 而且,它们之间不会创建数据依赖关系。
  2. 悲观处理关系: 在这种情况下,您假设相关对象可以由另一个用户独立获取和更新,因此相关对象必须存储为单独的缓存项。 然后,如果有人更新或删除相关对象,您希望您的主对象也从缓存中删除。 在这种情况下,您将在两个对象之间创建一个数据依赖关系。

下面是处理乐观情况的源代码。 请注意,主对象及其相关对象都被缓存为一个项目,因为主对象的序列化也将包括相关对象。

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

多对一关系的乐观处理

下面是处理悲观情况的源代码,因为乐观情况不需要使用任何数据依赖。

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

悲观处理多对一关系

上面的代码从数据库中加载了一个 Order 对象,并且 Customer 和 Product 对象都会自动加载它,因为 Order 对象与它们之间存在多对一的关系。 然后应用程序将 Customer 和 Product 对象添加到缓存中,然后将 Order 对象添加到缓存中,但同时依赖于 Customer 和 Product 对象。 这样,如果在缓存中更新或删除了这些 Customer 或 Product 对象中的任何一个,Order 对象会自动从缓存中删除以保持数据完整性。 应用程序不必跟踪这种关系。

处理一对多关系

每当您从缓存中获取了一个与另一个对象也具有一对多关系的对象时,您的持久性代码可能会同时加载主对象及其所有一对多相关对象的集合。 但是,并不总是需要加载相关对象,因为此时应用程序可能不需要它们。 如果您的持久性代码已加载相关对象,那么您需要在缓存中处理它们。 请注意,相关对象都保存在一个集合中,这会引入下面讨论的问题。

您可以通过三种方式处理此问题。 我将称一种乐观、一种温和悲观和一种真正悲观的方式,并将在下面解释它们中的每一个:

  1. 乐观处理关系: 在此,我们假设即使存在关系,其他人也不会单独修改相关对象。 任何想要修改相关对象的人都将通过缓存中的主对象获取它们,因此可以同时修改主对象和相关对象。 在这种情况下,我们不必将这两个对象分别存储在缓存中。 因此,主对象包含相关对象,并且它们都作为一个缓存项存储在缓存中。 而且,它们之间不会创建数据依赖关系。
  2. 对关系的轻度悲观处理: 在这种情况下,您假设可以独立获取相关对象,但只能作为整个集合而不是单个对象。 因此,您将集合存储为一个缓存项,并创建从集合到主对象的依赖关系。 然后,如果有人更新或删除了主要对象,您希望您的集合也从缓存中删除。
  3. 真正悲观的人际关系处理: 在这种情况下,您假设相关集合中的所有对象也可以由应用程序单独获取并进行修改。 因此,您不仅必须在缓存中存储集合,而且还必须将它们的所有单独对象分别存储在缓存中。 但是请注意,这可能会导致性能问题,因为您要多次访问缓存,这些缓存可能驻留在网络上的缓存服务器上。 我将在下一节“处理缓存中的集合”中讨论这个问题。

下面是一个如何乐观地处理一对多关系的示例。 请注意,包含相关对象的集合在放入缓存时会被序列化为主要对象的一部分。

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

乐观地处理一对多关系


下面是一个如何温和地处理一对多关系的例子。

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

温和悲观地处理一对多关系

在上面的示例中,与此相关的 Order 对象列表 对客户的 单独缓存。 整个集合被缓存为一个项目,因为我们假设没有人会直接单独修改单个 Order 对象。 应用程序将始终通过此 Customer 获取它,并再次修改和重新缓存整个集合。

另一种情况是一对多关系的悲观处理,这与我们处理缓存中集合的方式类似。该主题将在下一节中讨论。

处理缓存中的集合

在很多情况下,您会从数据库中获取对象集合。 这可能是由于您运行了一个查询,也可能是一对多关系在“多”端返回了相关对象的集合。 无论哪种方式,您得到的是必须在缓存中适当处理的对象集合。

有两种处理集合的方法,如下所述:

  1. 乐观处理集合: 在这种情况下,我们假设整个集合应该被缓存为一个项目,因为没有人会单独获取和修改保存在集合中的对象。 集合可能会被缓存一小段时间,这个假设可能非常有效。
  2. 对集合的悲观处理: 在这种情况下,我们假设集合中的单个对象可以单独获取和修改。 因此,我们缓存整个集合,然后缓存每个单独的对象,并创建从集合到各个对象的依赖关系。

下面是一个如何乐观地处理集合的示例。

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

乐观地处理收藏品

在上面的示例中,整个集合被缓存为一个项目,并且保存在集合中的所有 Customer 对象与集合和缓存一起自动序列化。 因此,无需在此处创建任何数据依赖项。

下面是一个如何悲观地处理集合的例子。

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

悲观地处理收藏品

在上面的示例中,集合中的每个对象都被缓存为一个单独的项目,然后整个集合以及一个项目都被缓存。 该集合对其所有单独缓存的对象具有数据依赖关系。 这样,如果这些对象中的任何一个被更新或删除,该集合也会从缓存中删除。


作者: 伊克巴尔汗 效劳于 Alachisoft ,一家领先的软件公司,提供 .NET 和 Java 分布式缓存、O/R 映射和 SharePoint 存储优化解决方案。 你可以联系他 伊克巴尔@alachisoft .

联系我们

联系电话
©版权所有 Alachisoft 2002 - 版权所有。 NCache 是 Diyatech Corp. 的注册商标。