ASP.NET

如何處理分散式緩存中的關聯資料

Iqbal Khan

 

Microsoft.net 框架已成為受歡迎的發展中國家的高事務性應用程式 (包括 Web、 面向服務的體系結構 (SOA)、 高性能計算或網格計算、 陣列和雲計算。 所有這些應用程式體系結構是可擴展的。 但一個主要的瓶頸出現在資料存儲區中 — — 通常是一個關係資料庫 — — 這並不能夠擴展和處理增加的事務的負載,可以在應用程式層。

其結果是,分散式緩存正在成為相當流行因為它允許您在比快得來訪問任何資料庫的記憶體中緩存中緩存資料。 此外,分散式緩存跨多個快取服務器提供線性擴展能力。 具有線性的可擴充性,你可以向分散式的快取叢集添加更多的高速快取服務器,如果您需要處理更大的事務負載。 Incre­心理增益中事務性的能力並不能降低在您添加更多的伺服器 (它是一條直線中 X Y 圖,其中 X 是高速快取服務器的數量,Y 是事務的能力)。 圖 1 顯示如何分散式緩存完全適合納入一個典型的 ASP.NET 或 Windows 通信基礎 (WCF) 應用程式以及它是如何提供線性的可擴充性。


圖 1 在 ASP.NET 或 Windows 通訊基礎應用程式中使用的分散式的緩存

在本文中,我將討論發展商應如何處理資料間的關係時緩存的資料。

雖然分散式緩存是偉大的它提供的一個挑戰是如何緩存關係的資料具有不同的資料元素之間的關係。 這是因為一個分散式的緩存為您提供了一個簡單的雜湊表樣 (金鑰,值) 其中"關鍵,"唯一地標識緩存的項,"價值"是您的資料或物件的介面。 但是,您的應用程式可能有一個複雜的域物件模型與繼承、 聚合和其他類型的關係。

此外,分散式的緩存存儲單個緩存的專案分開,這是不同的資料庫中您有不同的表之間的關係。 通常情況下,有沒有任何關係­之間的不同船緩存一個典型的分散式緩存中的專案。 這對.net 應用程式開發人員帶來了挑戰。 換句話說,你面臨跟蹤您的應用程式,以確保正確的緩存中,並在同一時間利用的分散式緩存的關係,您可以處理內部關係資訊很多的挑戰。

我將解釋如何這些關係可以映射和提供原始程式碼示例。 淨影響是應用程式不需要跟蹤的這些關係本身。 相反,緩存可以使他們意識到,並獨立處理。

物件關係映射是好

首先,請確保您轉換為關係資料欄的物件模型。 沒有這一步,你會很難處理的所有分散式緩存中關係。

在任何.net 應用程式中,您最有可能使用 DataReader (SqlDataReader、 OracleDataReader 或 OleDbDataReader) 或資料表中檢索資料的資料庫,這很好。 但許多開發人員然後直接存取資料從整個及其應用這些物件 (尤其是資料表)。 一些做了懶惰,因為他們不想要創建自訂的域物件。 別人做它,因為他們相信,他們可以在資料表中使用智慧過濾功能。

我強烈推薦您轉換您 DataReader 或資料­表到一個域的物件模型。 這將極大地簡化了應用程式設計,還允許您使用分散式緩存有效。 一個良好的分散式的緩存通常為您提供類似于 SQL 的或 LINQ 查詢的能力,所以您不會錯過的資料表的過濾功能。

當使用資料表時,你不得不緩存它作為一個緩存項。 請注意,減慢您的應用程式緩存它時的一大批資料表中的行。

您可以轉換 DataReader 與資料表中的域物件手動或使用領先的物件關係映射 (ORM) 引擎之一。 從微軟的實體框架是一個這種引擎。 另一種流行的一個是 NHibernate,這是開放原始碼。 ORM 工具大大簡化了您的任務的關係資料轉化為一個域的物件模型。

緩衝依賴可説明管理關係

與域物件模型中,第一個問題是如何處理那些在緩存中的所有關系。 而答案是緩衝依賴,而是 ASP.NET Cache 的一部分,現在還發現在某些商業的分散式緩存解決方案。

緩衝依賴,可以告知有關快取記憶體不同­ent 類型之間的關係的緩存條目,然後讓他們管理資料完整性的分散式的緩存。 基本上,緩衝依賴允許您告訴緩存一個緩存的專案依賴于另一個緩存項。 然後分散式的緩存可以跟蹤目標專案中的任何更改,並使不正確源項,取決於目標專案。

讓我們假設如果資料項目目 A 依賴于 B,然後如果 B 是不斷更新,或從緩存中刪除分散式的緩存會自動移除一也。 CacheDepend­ency 還提供了級聯功能,所以如果一個取決於對 B 和 B 上取決於 C,然後 C 被更新或刪除,否則會導致 B 自動刪除的分散式緩存中。 並當發生這種情況,一通過分散式緩存也自動刪除。 這被稱為層疊緩衝依賴。

我可以使用本文中稍後的緩衝依賴演示如何處理在分散式緩存中的關係。

三種不同類型的關係

首先,讓我們使用示例資料模型中所示的討論而言圖 2

Example Data Model for Relationships
圖 2 示例資料模型的關係

您可以看到,在此資料模型中,客戶有秩序 ; 一個一對多關聯性 產品有秩序 ; 一個一對多關聯性 與客戶和產品有與對方通過訂單表的多對多關係。 對於我們的示例資料模型, 圖 3 顯示的等效物件模型,它表示相同的關係。

Example Object Model Against the Data Model
針對資料模型圖 3 示例物件模型

我走進不同的關聯類型的詳細資訊之前,我想解釋一件事情。 與不同的資料庫中,應用程式域物件模型總有一個主要的物件應用程式已從資料庫中獲取,因此,它是在某一特定交易期間應用程式的起始點。 所有其他物件被視為有關這初級的物件。 這一概念是有效的所有類型的關係,並影響如何,您看到一個域的物件模型中的關係。 現在,讓我們繼續。

一對一和多對一關係

一對一和多對一關係都是類似的。 在表 A 和表 B 之間的一對一關聯性中,表 A 中的一行有關的表 B.中只有一個行 你在表 A 或 B,是另一個表的主鍵的外鍵。

在一個多對一關係和 B,您必須保留該外鍵表中 A. 此鍵是 B.表的主鍵 在一對一和多對一關係中外, 鍵具有唯一約束,以確保沒有重複。

同樣的關係很容易可以變成一個域的物件模型。 (較早前解釋) 的主物件保持對相關物件的引用。 圖 4 顯示保持關係資訊的主物件的示例。 請注意 Order 類包含對客戶類,以指示多對一關係的引用。 同樣的事情會發生甚至在一對一的關係。

圖 4 分別緩存相關的物件

public void CacheOrder(Order order)
{
  Cache cache = HttpRuntime.Cache;
  DateTime absolutionExpiration = Cache.NoAbsoluteExpiration;
  TimeSpan slidingExpiration = Cache.NoSlidingExpiration;
  CacheItemPriority priority = CacheItemPriority.Default;
  if (order != null) {
    // This will prevent Customer from being cached with Order
    Customer cust = order.OrderingCustomer;
    // Set orders to null so it doesn't get cached with Customer
    cust.Orders = null;
    string custKey = "Customer:CustomerId:" + cust.CustomerId;
    cache.Add(custKey, cust, null,
              absolutionExpiration,
              slidingExpiration,
              priority, null);
    // Dependency ensures order is removed if Cust updated/removed
    string[] keys = new string[1];
    keys[0] = custKey;
    CacheDependency dep = new CacheDependency(null, keys);
    string orderKey = "Order:CustomerId:" + order.CustomerId
      + ":ProductId:" + order.ProductId;
    // This will only cache Order object
    cache.Add(orderKey, order, dep,
              absolutionExpiration,
              slidingExpiration,
              priority, null);
  }
}

一個一對多關聯性 (多對一的反面)

如果表 A 和 B 之間資料庫中的一個一對多關聯性,表 B (含義"多面") 使外鍵,而是實際上的主鍵的表,但沒有外鍵的唯一約束。

在對多個域物件模型中,主要物件是客戶和相關的物件是順序。 所以客戶物件包含的命令物件的集合。 圖 4 也顯示了這種關係在客戶和訂單的物件之間的示例。

多對多關聯性

如果表 A 和 B 之間的多對多關係,總有一個中間表 AB. 在我們的情況下,命令是仲介的表,並有兩個外鍵。 一是對客戶表來表示多對一關係,另一種是針對要再次代表多對一關係的順序表。

多對多關係,如果該物件模型通常是一到-多從主物件 (較早時定義) 的角度來看,是客戶或產品。 物件模型現在還包含仲介的物件 (在我們的例子中,順序) 和其他物件 (產品,在我們的例子) 之間的多對一關係。 圖 4 也顯示了一個示例的多對多關係,在這種情況下是客戶和產品之間的物件,並被仲介物件的命令物件。

正如您所看到的從客戶物件的主要物件,該物件模型是與該應用程式從資料庫中獲取它。 所有相關物件都是從這個主要的物件的角度來看。 所以客戶物件會有命令物件的集合,並且每個命令物件將包含產品物件的引用。 產品物件可能不會包含屬於產品物件,因為,這裡並不需要使用的所有命令物件的集合。 如果產品的主物件,它便命令物件的集合,— — 但客戶物件不會再有命令物件的集合。

不同的關聯類型的緩存策略

到目前為止,討論了如何從資料庫讀取資料,將其改造成一個域的物件模型以及與資料庫域物件模型中保持相同的關係 — — 雖然從主物件的角度。 但是,如果您的應用程式需要在分散式緩存中緩存資料,您必須瞭解如何處理這些緩存中的所有關系。 我會去通過每宗個案。

在所有情況下,您必須確保在緩存中的關係資訊不丟失,會影響資料完整性或您稍後從緩存讀取相關的物件的能力。

緩存的一對一和多對一關係

這裡有兩個選項:

  1. 與緩存相關的主物件的物件: 此選項假定的相關的物件不打算由另一個使用者進行修改,儘管它是在緩存中,所以它是安全的緩存它作為主要物件作為一個緩存項的一部分。 如果你害怕這並不是這樣,則不要使用此選項。
  2. 與緩存相關的物件分開: 此選項假定它是在緩存中,所以最好是緩存的主要和相關的物件作為單獨的緩存項的同時,他們可能會更新由另一個使用者的相關的物件。 您應該在兩個物件的每個指定唯一的緩存鍵。 此外,可以使用分散式緩存的標記功能來標記為有一個到主物件關係的相關的物件。 然後您可以獲取它後來通過標記。

緩存的一對多關聯性

在一個一對多關聯性,您主要的物件總是"一方"(在我們的示例中它是客戶物件)。 主物件包含的命令物件的集合。 相關物件的每個集合表示一個一對多關聯性。 在這裡你有三個緩存選項:

  1. 快取記憶體與主要物件相關的物件集合: 這當然是假定相關的物件不會被更新或獨立回遷由另一個使用者在緩存中,因此它是安全的它們為主要物件的一部分進行緩存。 這樣做可以提高您的性能,因為你可以獲取一個緩存調用中的一切。 但是,如果該集合是大 (意味著有成千上萬的物件和進入百萬位元組的資料),你就不要的性能增益。
  2. 單獨緩存相關的物件集合: 在此情況下,您認為其他使用者可能想要從緩存中 ; 獲取同一集合 因此,它有意義分別緩存相關物件的集合。 你應該結構你這樣您將能夠找到一些關於您主要的物件資訊基於此集合中的緩存鍵。 我將討論問題的緩存在本文中稍後介紹的更多詳細資訊的集合。
  3. 單獨緩存集合中的所有個人相關的物件: 在此情況下,您認為相關的集合中的每個物件可能會更新由其他使用者 ; 因此,你不能保持一個物件在集合中,並且必須單獨緩存。 可以使用分散式緩存的標記功能來標識作為您主要的物件相關的所有物件,所以,你應該能夠快速稍後上讀取它們。

緩存多對多關係

多對多關係的域物件模型中並不真正存在。 相反,他們被代表一個一對多關聯性,中間物件 (在我們的例子中,順序) 包含對其他側物件 (在我們的例子中,產品) 的引用的例外。 在純一到多,此引用不存在。

處理集合

如何處理收藏集的主題是一個有趣的問題,因為經常從資料庫讀取物件的集合,並且您想要能夠緩存收藏的一種有效方式。

假設您有一種情況,在您請求您基於紐約的客戶,你別指望任何新的客戶要添加來自紐約,在第二天或在接下來的幾分鐘。 在保持介意你不緩存資料的幾個星期和幾個月,只為一分鐘或幾小時,在大多數情況下。 在某些情況下許多天緩存可能會。

有不同的緩存策略用於緩存的收藏集,我會解釋。

緩存整個集合作為一個專案

在這種情況下,你知道你不會添加任何更多的客戶從紐約和其他使用者沒有訪問和修改任何紐約客戶資料期間,將緩存資料。 因此,您可以緩存紐約客戶作為一個緩存項的整個集合。 在這裡,您可以搜尋條件或緩存鍵的 SQL 查詢部分。 您想要獲取來自紐約,是的客戶的任何時間你只是轉到快取記憶體和說,"給我集合,其中包含紐約的客戶。

圖 5 顯示如何緩存相關的命令物件的整個集合。

圖 5 分別緩存相關的集合

public void CacheCustomer(Customer cust)
{
  Cache cache = HttpRuntime.Cache;
  DateTime absolutionExpiration = Cache.NoAbsoluteExpiration;
  TimeSpan slidingExpiration = Cache.NoSlidingExpiration;
  CacheItemPriority priority = CacheItemPriority.Default;
  if (cust != null)
  {
    string key = "Customer:CustomerId:" + cust.CustomerId;
    // Let's preserve it to cache separately
    IList<Order> orderList = cust.Orders;
    // So it doesn't get cached as part of Customer
    cust.Orders = null;
    // This will only cache Customer object
    cache.Add(key, cust, null,
              absolutionExpiration,
              slidingExpiration,
              priority, null);
    // See that this key is also Customer based
    key = "Customer:CustomerId:" + cust.CustomerId + ":Orders";
    cache.Add(key, orderList, null,
              absolutionExpiration,
              slidingExpiration,
              priority, null);
  }
}

單獨緩存集合的每個專案

現在讓我們轉到第二個策略。 在這種情況下,您或其他使用者想要單獨地讀取和修改紐約客戶。 但以前的戰略將需要每個人都要從快取記憶體中讀取整個集合、 修改這一個客戶、 把它放回集合和快取記憶體設置為集合。 如果你做這經常不夠,它將成為不切實際的性能方面的原因。

所以,在此實例中,不要讓紐約的所有客戶作為一個緩存項的集合。 你分手集合,並將客戶的每個物件分別存儲在緩存中。 您需要組合所有這些客戶物件,以便您可以唯讀取它們以後點作為一個集合或 IDictionary 所有回檔用一次。 這種方法的好處能夠讀取和修改單個客戶物件。 圖 6 顯示如何緩存相關物件在集合中的每個單獨的示例。

圖 6 分別緩存每個集合項

public void CacheOrdersListItems(IList<Order> ordersList)
{
  Cache cache = HttpRuntime.Cache;
  DateTime absolutionExpiration = Cache.NoAbsoluteExpiration;
  TimeSpan slidingExpiration = Cache.NoSlidingExpiration;
  CacheItemPriority priority = CacheItemPriority.Default;
  foreach (Order order in ordersList)
  {
    string key = "Order:CustomerId:" + order.CustomerId
      + ":ProductId" + order.ProductId;
    string[] keys = new string[1];
    keys[0] = key;
    // Dependency ensures Order is removed if Cust updated/removed
    CacheDependency dep = new CacheDependency(null, keys);
    Tag [] tagList = new Tag [1];
    tagList[0] = new Tag ("customerId" + order.CustomerId);
     // Tag allows us to find order with a customerId alone
     cache.Add(key, order, dep,
               absolutionExpiration,
               slidingExpiration,
               priority, null, tagList);
  }
}

請記住,然而,這一戰略假定它們已經緩存的時期,有已沒有紐約客戶添加到資料庫。 否則,當您從緩存讀取紐約的所有客戶,你會只是部分的清單。 同樣地,如果從資料庫,但不是從緩存中刪除一個客戶,則您將獲得客戶的陳舊清單。

處理集合物件添加或刪除

用於處理集合的第三個選項是您認為新在哪裡的客戶可能會添加從紐約或一些現有的客戶可能會被刪除。 在這種情況下,無論您的緩存是只有部分資料或舊的資料。 也許集合了只有 100 個客戶,您將添加兩個更多的今天。 這兩個不會緩存的一部分。 但是,當您從紐約讀取的所有客戶,您要正確的資料: 你想 102 的結果,不 100。

確保發生這種情況的方法是向資料庫發出呼叫。 為客戶獲得的所有 Id 和做比較,看看幾個在緩存中,哪些不是。 單獨獲取從資料庫不在緩存中,並將它們添加到緩存中的那些。 正如您所看到的這並不是一個快速的過程 ; 你讓多個資料庫調用和緩存的多個調用。 分散式緩存,提供基本的功能,如果你有 1000 個客戶的集合,並添加新的 50,你就會最終作出 51 資料庫調用和 101 分散式的緩存調用。 在這種情況下,它可能會更快只是為了從資料庫中調用一次讀取集合。

但如果分散式的緩存提供了大容量操作,您將使一個資料庫調用讀取 Id、 一個分散式的緩存調用,看看哪個在快取記憶體中存在的 Id、 調用一次添加到緩存中所有這些新的客戶和從緩存中獲取客戶的整個集合調用一次。 這將是共有一個資料庫調用和三個分散式的緩存調用,而這根本不是壞。 並且,如果沒有新的客戶添加了 (這將 90%的時間是如此),這將減少到一個資料庫調用和兩個分散式的緩存調用。

效能及延展性

我介紹當您的應用程式從資料庫中讀取關係資料將它轉換為一個域的物件模型,然後想要緩存的物件時,出現的各種情況。 我的目的是文章的要突出顯示所有物件關係而影響您如何緩存物件以及如何你稍後修改或從緩存中移除這些領域。

分散式緩存是很好地提高應用程式的性能和可擴充性。 而且,因為應用程式處理關係資料的大部分時間,我希望這篇文章已向您提供一些深入瞭解應該如何處理分散式緩存中的關聯資料。

Iqbal Khan 是 Alachisoft,它提供 NCache 和 StorageEdge 主席科技宣傳員 (alachisoft.com)。NCache 是一個分散式的.net 和 JAVA 緩存,並提高了應用程式的性能和可擴充性。StorageEdge 是 SharePoint 蘇格蘭皇家銀行 (rbs) 供應商,説明優化存儲和 SharePoint 的性能。汗在 1990 年收到布隆明頓、 美國印第安那大學的電腦科學碩士學位。 他在到達 iqbal@alachisoft.com

由於有關檢討這篇文章是以下技術專家: Damian Edwards