作者:伊克巴尔汗
分布式缓存可让您极大地提高应用程序性能和可扩展性。 应用程序性能得到了提高,因为内存中缓存的数据访问速度比数据库快得多。 而且,可扩展性是通过将缓存增加到多个服务器作为分布式缓存来实现的,不仅可以获得更多的存储容量,还可以获得更多的每秒事务吞吐量。
尽管有如此强大的优势,但许多内存缓存仍面临一个问题。 这与大多数数据是关系的事实有关,而缓存通常是具有键值对概念的简单哈希表。 每个项目都独立存储在缓存中,而无需了解任何其他相关项目。 并且,这使得应用程序很难跟踪不同缓存项之间的关系,以便在获取它们以及在更新或删除一个项目并且其相关项目也在数据库中更新或删除的情况下保持数据完整性。 发生这种情况时,缓存不知道它并且无法处理它。
典型的现实应用程序处理与数据库中的其他数据元素具有一对一、多对一、一对多和多对多关系的关系数据。 这需要在不同的相关数据元素之间保持参照完整性。 因此,为了 保持缓存中的数据完整性,缓存必须理解这些关系并保持相同的参照完整性。
为了处理这些情况,微软在 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;
}
}
本文使用以下示例来演示如何在缓存中处理各种类型的关系。
在上图中,显示了以下关系:
针对上述关系,设计了以下领域对象。
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 对象。
下面,我将展示如何在缓存中处理这些关系。
每当您从缓存中获取与另一个对象也具有一对一或多对一关系的对象时,您的持久性代码也可能已经加载了相关对象。 但是,并不总是需要加载相关对象,因为当时应用程序可能不需要它。 如果您的持久性代码已加载相关对象,那么您需要处理它。
有两种方法可以处理这个问题。 我将称一种乐观方式和另一种悲观方式,并将在下面解释它们中的每一个:
下面是处理乐观情况的源代码。 请注意,主对象及其相关对象都被缓存为一个项目,因为主对象的序列化也将包括相关对象。
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 对象会自动从缓存中删除以保持数据完整性。 应用程序不必跟踪这种关系。
每当您从缓存中获取了一个与另一个对象也具有一对多关系的对象时,您的持久性代码可能会同时加载主对象及其所有一对多相关对象的集合。 但是,并不总是需要加载相关对象,因为此时应用程序可能不需要它们。 如果您的持久性代码已加载相关对象,那么您需要在缓存中处理它们。 请注意,相关对象都保存在一个集合中,这会引入下面讨论的问题。
您可以通过三种方式处理此问题。 我将称一种乐观、一种温和悲观和一种真正悲观的方式,并将在下面解释它们中的每一个:
下面是一个如何乐观地处理一对多关系的示例。 请注意,包含相关对象的集合在放入缓存时会被序列化为主要对象的一部分。
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 获取它,并再次修改和重新缓存整个集合。
另一种情况是一对多关系的悲观处理,这与我们处理缓存中集合的方式类似。该主题将在下一节中讨论。
在很多情况下,您会从数据库中获取对象集合。 这可能是由于您运行了一个查询,也可能是一对多关系在“多”端返回了相关对象的集合。 无论哪种方式,您得到的是必须在缓存中适当处理的对象集合。
有两种处理集合的方法,如下所述:
下面是一个如何乐观地处理集合的示例。
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 .