佳木斯湛栽影视文化发展公司

主頁 > 知識庫 > 《解剖PetShop》之四:PetShop之ASP.NET緩存

《解剖PetShop》之四:PetShop之ASP.NET緩存

熱門標(biāo)簽:智能手機(jī) 蘋果 電子圍欄 解決方案 呼叫中心 地方門戶網(wǎng)站 服務(wù)器配置 硅谷的囚徒呼叫中心

四 PetShop之ASP.NET緩存

  如果對微型計(jì)算機(jī)硬件系統(tǒng)有足夠的了解,那么我們對于Cache這個(gè)名詞一定是耳熟能詳?shù)摹T贑PU以及主板的芯片中,都引入了這種名為高速緩沖存儲器(Cache)的技術(shù)。因?yàn)镃ache的存取速度比內(nèi)存快,因而引入Cache能夠有效的解決CPU與內(nèi)存之間的速度不匹配問題。硬件系統(tǒng)可以利用Cache存儲CPU訪問概率高的那些數(shù)據(jù),當(dāng)CPU需要訪問這些數(shù)據(jù)時(shí),可以直接從Cache中讀取,而不必訪問存取速度相對較慢的內(nèi)存,從而提高了CPU的工作效率。軟件設(shè)計(jì)借鑒了硬件設(shè)計(jì)中引入緩存的機(jī)制以改善整個(gè)系統(tǒng)的性能,尤其是對于一個(gè)數(shù)據(jù)庫驅(qū)動的Web應(yīng)用程序而言,緩存的利用是不可或缺的,畢竟,數(shù)據(jù)庫查詢可能是整個(gè)Web站點(diǎn)中調(diào)用最頻繁但同時(shí)又是執(zhí)行最緩慢的操作之一,我們不能被它老邁的雙腿拖緩我們前進(jìn)的征程。緩存機(jī)制正是解決這一缺陷的加速器。

4.1  ASP.NET緩存概述

  作為.Net框架下開發(fā)Web應(yīng)用程序的主打產(chǎn)品,ASP.NET充分考慮了緩存機(jī)制。通過某種方法,將系統(tǒng)需要的數(shù)據(jù)對象、Web頁面存儲在內(nèi)存中,使得Web站點(diǎn)在需要獲取這些數(shù)據(jù)時(shí),不需要經(jīng)過繁瑣的數(shù)據(jù)庫連接、查詢和復(fù)雜的邏輯運(yùn)算,就可以“觸手可及”,如“探囊取物”般容易而快速,從而提高整個(gè)Web系統(tǒng)的性能。

  ASP.NET提供了兩種基本的緩存機(jī)制來提供緩存功能。一種是應(yīng)用程序緩存,它允許開發(fā)者將程序生成的數(shù)據(jù)或報(bào)表業(yè)務(wù)對象放入緩存中。另外一種緩存機(jī)制是頁輸出緩存,利用它,可以直接獲取存放在緩存中的頁面,而不需要經(jīng)過繁雜的對該頁面的再次處理。

  應(yīng)用程序緩存其實(shí)現(xiàn)原理說來平淡無奇,僅僅是通過ASP.NET管理內(nèi)存中的緩存空間。放入緩存中的應(yīng)用程序數(shù)據(jù)對象,以鍵/值對的方式存儲,這便于用戶在訪問緩存中的數(shù)據(jù)項(xiàng)時(shí),可以根據(jù)key值判斷該項(xiàng)是否存在緩存中。

  放入在緩存中的數(shù)據(jù)對象其生命周期是受到限制的,即使在整個(gè)應(yīng)用程序的生命周期里,也不能保證該數(shù)據(jù)對象一直有效。ASP.NET可以對應(yīng)用程序緩存進(jìn)行管理,例如當(dāng)數(shù)據(jù)項(xiàng)無效、過期或內(nèi)存不足時(shí)移除它們。此外,調(diào)用者還可以通過CacheItemRemovedCallback委托,定義回調(diào)方法使得數(shù)據(jù)項(xiàng)被移除時(shí)能夠通知用戶。

  在.Net Framework中,應(yīng)用程序緩存通過System.Web.Caching.Cache類實(shí)現(xiàn)。它是一個(gè)密封類,不能被繼承。對于每一個(gè)應(yīng)用程序域,都要創(chuàng)建一個(gè)Cache類的實(shí)例,其生命周期與應(yīng)用程序域的生命周期保持一致。我們可以利用Add或Insert方法,將數(shù)據(jù)項(xiàng)添加到應(yīng)用程序緩存中,如下所示:

Cache["First"] = "First Item";
Cache.Insert("Second", "Second Item");

我們還可以為應(yīng)用程序緩存添加依賴項(xiàng),使得依賴項(xiàng)發(fā)生更改時(shí),該數(shù)據(jù)項(xiàng)能夠從緩存中移除:

string[] dependencies = {"Second"};
Cache.Insert("Third", "Third Item",
new System.Web.Caching.CacheDependency(null, dependencies));

與之對應(yīng)的是緩存中數(shù)據(jù)項(xiàng)的移除。前面提到ASP.NET可以自動管理緩存中項(xiàng)的移除,但我們也可以通過代碼編寫的方式顯式的移除相關(guān)的數(shù)據(jù)項(xiàng):

Cache.Remove("First");

  相對于應(yīng)用程序緩存而言,頁輸出緩存的應(yīng)用更為廣泛。它可以通過內(nèi)存將處理后的ASP.NET頁面存儲起來,當(dāng)客戶端再一次訪問該頁面時(shí),可以省去頁面處理的過程,從而提高頁面訪問的性能,以及Web服務(wù)器的吞吐量。例如,在一個(gè)電子商務(wù)網(wǎng)站里,用戶需要經(jīng)常查詢商品信息,這個(gè)過程會涉及到數(shù)據(jù)庫訪問以及搜索條件的匹配,在數(shù)據(jù)量較大的情況下,如此的搜索過程是較為耗時(shí)的。此時(shí),利用頁輸出緩存就可以將第一次搜索得到的查詢結(jié)果頁存儲在緩存中。當(dāng)用戶第二次查詢時(shí),就可以省去數(shù)據(jù)查詢的過程,減少頁面的響應(yīng)時(shí)間。

  頁輸出緩存分為整頁緩存和部分頁緩存。我們可以通過@OutputCache指令完成對Web頁面的輸出緩存。它主要包含兩個(gè)參數(shù):Duration和VaryByParam。Duration參數(shù)用于設(shè)置頁面或控件進(jìn)行緩存的時(shí)間,其單位為秒。如下的設(shè)置表示緩存在60秒內(nèi)有效:

%@ OutputCache Duration=“60“ VaryByParam=“none“ %>

  只要沒有超過Duration設(shè)置的期限值,當(dāng)用戶訪問相同的頁面或控件時(shí),就可以直接在緩存中獲取。
使用VaryByParam參數(shù)可以根據(jù)設(shè)置的參數(shù)值建立不同的緩存。例如在一個(gè)輸出天氣預(yù)報(bào)結(jié)果的頁面中,如果需要為一個(gè)ID為txtCity的TextBox控件建立緩存,其值將顯示某城市的氣溫,那么我們可以進(jìn)行如下的設(shè)置:

%@ OutputCache Duration=”60” VaryByParam=”txtCity” %>

  如此一來,ASP.NET會對txtCity控件的值進(jìn)行判斷,只有輸入的值與緩存值相同,才從緩存中取出相應(yīng)的值。這就有效地避免了因?yàn)橹档牟煌鴮?dǎo)致輸出錯(cuò)誤的數(shù)據(jù)。

  利用緩存的機(jī)制對性能的提升非常明顯。通過ACT(Application Center Test)的測試,可以發(fā)現(xiàn)設(shè)置緩存后執(zhí)行的性能比未設(shè)置緩存時(shí)的性能足足提高三倍多。

  引入緩存看來是提高性能的“完美”解決方案,然而“金無足赤,人無完人”,緩存機(jī)制也有缺點(diǎn),那就是數(shù)據(jù)過期的問題。一旦應(yīng)用程序數(shù)據(jù)或者頁面結(jié)果值發(fā)生的改變,那么在緩存有效期范圍內(nèi),你所獲得的結(jié)果將是過期的、不準(zhǔn)確的數(shù)據(jù)。我們可以想一想股票系統(tǒng)利用緩存所帶來的災(zāi)難,當(dāng)你利用錯(cuò)誤過期的數(shù)據(jù)去分析股市的風(fēng)云變幻時(shí),你會發(fā)現(xiàn)獲得的結(jié)果真可以說是“失之毫厘,謬以千里”,看似大好的局面就會像美麗的泡沫一樣,用針一戳,轉(zhuǎn)眼就消失得無影無蹤。

  那么我們是否應(yīng)該為了追求高性能,而不顧所謂“數(shù)據(jù)過期”所帶來的隱患呢?顯然,在類似于股票系統(tǒng)這種數(shù)據(jù)更新頻繁的特定場景下,數(shù)據(jù)過期的糟糕表現(xiàn)甚至比低效的性能更讓人難以接受。故而,我們需要在性能與數(shù)據(jù)正確性間作出權(quán)衡。所幸的是,.Net Framework 2.0引入了一種新的緩存機(jī)制,它為我們的“魚與熊掌兼得”帶來了技術(shù)上的可行性。

  .Net 2.0引入的自定義緩存依賴項(xiàng),特別是基于MS-SQL Server的SqlCacheDependency特性,使得我們可以避免“數(shù)據(jù)過期”的問題,它能夠根據(jù)數(shù)據(jù)庫中相應(yīng)數(shù)據(jù)的變化,通知緩存,并移除那些過期的數(shù)據(jù)。事實(shí)上,在PetShop 4.0中,就充分地利用了SqlCacheDependency特性。

4.2 SqlCacheDependency特性

  SqlCacheDependency特性實(shí)際上是通過System.Web.Caching.SqlCacheDependency類來體現(xiàn)的。通過該類,可以在所有支持的SQL Server版本(7.0,2000,2005)上監(jiān)視特定的SQL Server數(shù)據(jù)庫表,并創(chuàng)建依賴于該表以及表中數(shù)據(jù)行的緩存項(xiàng)。當(dāng)數(shù)據(jù)表或表中特定行的數(shù)據(jù)發(fā)生更改時(shí),具有依賴項(xiàng)的數(shù)據(jù)項(xiàng)就會失效,并自動從Cache中刪除該項(xiàng),從而保證了緩存中不再保留過期的數(shù)據(jù)。
由于版本的原因,SQL Server 2005完全支持SqlCacheDependency特性,但對于SQL Server 7.0和SQL Server 2000而言,就沒有如此幸運(yùn)了。畢竟這些產(chǎn)品出現(xiàn)在.Net Framework 2.0之前,因此它并沒有實(shí)現(xiàn)自動監(jiān)視數(shù)據(jù)表數(shù)據(jù)變化,通知ASP.NET的功能。解決的辦法就是利用輪詢機(jī)制,通過ASP.NET進(jìn)程內(nèi)的一個(gè)線程以指定的時(shí)間間隔輪詢SQL Server數(shù)據(jù)庫,以跟蹤數(shù)據(jù)的變化情況。

  要使得7.0或者2000版本的SQL Server支持SqlCacheDependency特性,需要對數(shù)據(jù)庫服務(wù)器執(zhí)行相關(guān)的配置步驟。有兩種方法配置SQL Server:使用aspnet_regsql命令行工具,或者使用SqlCacheDependencyAdmin類。

4.2.1  利用aspnet_regsql工具

aspnet_regsql工具位于Windows\Microsoft.NET\Framework\[版本]文件夾中。如果直接雙擊該工具的執(zhí)行文件,會彈出一個(gè)向?qū)υ捒?,提示我們完成相?yīng)的操作:


圖4-1 aspnet_regsql工具

如圖4-1所示中的提示信息,說明該向?qū)е饕糜谂渲肧QL Server數(shù)據(jù)庫,如membership,profiles等信息,如果要配置SqlCacheDependency,則需要以命令行的方式執(zhí)行。以PetShop 4.0為例,數(shù)據(jù)庫名為MSPetShop4,則命令為:

aspnet_regsql -S localhost -E -d MSPetShop4 -ed

以下是該工具的命令參數(shù)說明:

-?  顯示該工具的幫助功能;
-S  后接的參數(shù)為數(shù)據(jù)庫服務(wù)器的名稱或者IP地址;
-U  后接的參數(shù)為數(shù)據(jù)庫的登陸用戶名;
-P  后接的參數(shù)為數(shù)據(jù)庫的登陸密碼;
-E  當(dāng)使用windows集成驗(yàn)證時(shí),使用該功能;
-d  后接參數(shù)為對哪一個(gè)數(shù)據(jù)庫采用SqlCacheDependency功能;
-t  后接參數(shù)為對哪一個(gè)表采用SqlCacheDependency功能;
-ed  允許對數(shù)據(jù)庫使用SqlCacheDependency功能;
-dd  禁止對數(shù)據(jù)庫采用SqlCacheDependency功能;
-et  允許對數(shù)據(jù)表采用SqlCacheDependency功能;
-dt  禁止對數(shù)據(jù)表采用SqlCacheDependency功能;
-lt  列出當(dāng)前數(shù)據(jù)庫中有哪些表已經(jīng)采用sqlcachedependency功能。

  以上面的命令為例,說明將對名為MSPetShop4的數(shù)據(jù)庫采用SqlCacheDependency功能,且SQL Server采用了windows集成驗(yàn)證方式。我們還可以對相關(guān)的數(shù)據(jù)表執(zhí)行aspnet_regsql命令,如:

aspnet_regsql -S localhost -E -d MSPetShop4 -t Item -et
aspnet_regsql -S localhost -E -d MSPetShop4 -t Product -et
aspnet_regsql -S localhost -E -d MSPetShop4 -t Category -et

  當(dāng)執(zhí)行上述的四條命令后,aspnet_regsql工具會在MSPetShop4數(shù)據(jù)庫中建立一個(gè)名為AspNet_SqlCacheTablesForChangeNotification的新數(shù)據(jù)庫表。該數(shù)據(jù)表包含三個(gè)字段。字段tableName記錄要追蹤的數(shù)據(jù)表的名稱,例如在PetShop 4.0中,要記錄的數(shù)據(jù)表就包括Category、Item和Product。notificationCreated字段記錄開始追蹤的時(shí)間。changeId作為一個(gè)類型為int的字段,用于記錄數(shù)據(jù)表數(shù)據(jù)發(fā)生變化的次數(shù)。如圖4-2所示:


圖4-2 AspNet_SqlCacheTablesForChangeNotification數(shù)據(jù)表

  除此之外,執(zhí)行該命令還會為MSPetShop4數(shù)據(jù)庫添加一組存儲過程,為ASP.NET提供查詢追蹤的數(shù)據(jù)表的情況,同時(shí)還將為使用了SqlCacheDependency的表添加觸發(fā)器,分別對應(yīng)Insert、Update、Delete等與數(shù)據(jù)更改相關(guān)的操作。例如Product數(shù)據(jù)表的觸發(fā)器:

CREATE TRIGGER dbo.[Product_AspNet_SqlCacheNotification_Trigger] ON [Product]
 FOR INSERT, UPDATE, DELETE AS BEGIN
 SET NOCOUNT ON
 EXEC dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure N'Product'
END

其中,AspNet_SqlCacheUpdateChangeIdStoredProcedure即是工具添加的一組存儲過程中的一個(gè)。當(dāng)對Product數(shù)據(jù)表執(zhí)行Insert、Update或Delete等操作時(shí),就會激活觸發(fā)器,然后執(zhí)行AspNet_SqlCacheUpdateChangeIdStoredProcedure存儲過程。其執(zhí)行的過程就是修改AspNet_SqlCacheTablesForChangeNotification數(shù)據(jù)表的changeId字段值:

CREATE PROCEDURE dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure
 @tableName NVARCHAR(450)
 AS
 BEGIN
 UPDATE dbo.AspNet_SqlCacheTablesForChangeNotification WITH (ROWLOCK) SET changeId = changeId + 1
 WHERE tableName = @tableName
 END 
GO

4.2.2  利用SqlCacheDependencyAdmin類

我們也可以利用編程的方式來來管理數(shù)據(jù)庫對SqlCacheDependency特性的使用。該類包含了五個(gè)重要的方法:

DisableNotifications
為特定數(shù)據(jù)庫禁用 SqlCacheDependency對象更改通知
DisableTableForNotifications
為數(shù)據(jù)庫中的特定表禁用SqlCacheDependency對象更改通知
EnableNotifications
為特定數(shù)據(jù)庫啟用SqlCacheDependency對象更改通知
EnableTableForNotifications
為數(shù)據(jù)庫中的特定表啟用SqlCacheDependency對象更改通知
GetTablesEnabledForNotifications
返回啟用了SqlCacheDependency對象更改通知的所有表的列表

表4-1 SqlCacheDependencyAdmin類的主要方法

假設(shè)我們定義了如下的數(shù)據(jù)庫連接字符串:

const string connectionStr = "Server=localhost;Database=MSPetShop4";

那么為數(shù)據(jù)庫MSPetShop4啟用SqlCacheDependency對象更改通知的實(shí)現(xiàn)為:

protected void Page_Load(object sender, EventArgs e)
{
 if (!IsPostBack)
 {
 SqlCacheDependencyAdmin.EnableNotifications(connectionStr);
 }
}

為數(shù)據(jù)表Product啟用SqlCacheDependency對象更改通知的實(shí)現(xiàn)則為:

SqlCacheDependencyAdmin.EnableTableForNotifications(connectionStr, "Product");

  如果要調(diào)用表4-1中所示的相關(guān)方法,需要注意的是訪問SQL Server數(shù)據(jù)庫的帳戶必須具有創(chuàng)建表和存儲過程的權(quán)限。如果要調(diào)用EnableTableForNotifications方法,還需要具有在該表上創(chuàng)建SQL Server觸發(fā)器的權(quán)限。

  雖然說編程方式賦予了程序員更大的靈活性,但aspnet_regsql工具卻提供了更簡單的方法實(shí)現(xiàn)對SqlCacheDependency的配置與管理。PetShop 4.0采用的正是aspnet_regsql工具的辦法,它編寫了一個(gè)文件名為InstallDatabases.cmd的批處理文件,其中包含了對aspnet_regsql工具的執(zhí)行,并通過安裝程序去調(diào)用該文件,實(shí)現(xiàn)對SQL Server的配置。

4.3 在PetShop 4.0中ASP.NET緩存的實(shí)現(xiàn)

  PetShop作為一個(gè)B2C的寵物網(wǎng)上商店,需要充分考慮訪客的用戶體驗(yàn),如果因?yàn)閿?shù)據(jù)量大而導(dǎo)致Web服務(wù)器的響應(yīng)不及時(shí),頁面和查詢數(shù)據(jù)遲遲得不到結(jié)果,會因此而破壞客戶訪問網(wǎng)站的心情,在耗盡耐心的等待后,可能會失去這一部分客戶。無疑,這是非常糟糕的結(jié)果。因而在對其進(jìn)行體系架構(gòu)設(shè)計(jì)時(shí),整個(gè)系統(tǒng)的性能就顯得殊為重要。然而,我們不能因噎廢食,因?yàn)閷W⒂谛阅芏雎詳?shù)據(jù)的正確性。在PetShop 3.0版本以及之前的版本,因?yàn)锳SP.NET緩存的局限性,這一問題并沒有得到很好的解決。PetShop 4.0則引入了SqlCacheDependency特性,使得系統(tǒng)對緩存的處理較之以前大為改觀。

4.3.1  CacheDependency接口

  PetShop 4.0引入了SqlCacheDependency特性,對Category、Product和Item數(shù)據(jù)表對應(yīng)的緩存實(shí)施了SQL Cache Invalidation技術(shù)。當(dāng)對應(yīng)的數(shù)據(jù)表數(shù)據(jù)發(fā)生更改后,該技術(shù)能夠?qū)⑾嚓P(guān)項(xiàng)從緩存中移除。實(shí)現(xiàn)這一技術(shù)的核心是SqlCacheDependency類,它繼承了CacheDependency類。然而為了保證整個(gè)架構(gòu)的可擴(kuò)展性,我們也允許設(shè)計(jì)者建立自定義的CacheDependency類,用以擴(kuò)展緩存依賴。這就有必要為CacheDependency建立抽象接口,并在web.config文件中進(jìn)行配置。

  在PetShop 4.0的命名空間PetShop.ICacheDependency中,定義了名為IPetShopCacheDependency接口,它僅包含了一個(gè)接口方法:

public interface IPetShopCacheDependency
{ 
 AggregateCacheDependency GetDependency();
}

  AggregateCacheDependency是.Net Framework 2.0新增的一個(gè)類,它負(fù)責(zé)監(jiān)視依賴項(xiàng)對象的集合。當(dāng)這個(gè)集合中的任意一個(gè)依賴項(xiàng)對象發(fā)生改變時(shí),該依賴項(xiàng)對象對應(yīng)的緩存對象都將被自動移除。

  AggregateCacheDependency類起到了組合CacheDependency對象的作用,它可以將多個(gè)CacheDependency對象甚至于不同類型的CacheDependency對象與緩存項(xiàng)建立關(guān)聯(lián)。由于PetShop需要為Category、Product和Item數(shù)據(jù)表建立依賴項(xiàng),因而IPetShopCacheDependency的接口方法GetDependency()其目的就是返回建立了這些依賴項(xiàng)的AggregateCacheDependency對象。

4.3.2  CacheDependency實(shí)現(xiàn)

CacheDependency的實(shí)現(xiàn)正是為Category、Product和Item數(shù)據(jù)表建立了對應(yīng)的SqlCacheDependency類型的依賴項(xiàng),如代碼所示:

public abstract class TableDependency : IPetShopCacheDependency
{
 // This is the separator that's used in web.config
 protected char[] configurationSeparator = new char[] { ',' };

 protected AggregateCacheDependency dependency = new AggregateCacheDependency();
 protected TableDependency(string configKey)
 {
 string dbName = ConfigurationManager.AppSettings["CacheDatabaseName"];
 string tableConfig = ConfigurationManager.AppSettings[configKey];
 string[] tables = tableConfig.Split(configurationSeparator);

 foreach (string tableName in tables)
  dependency.Add(new SqlCacheDependency(dbName, tableName));
 }
 public AggregateCacheDependency GetDependency()
 {
 return dependency;
 }
}

需要建立依賴項(xiàng)的數(shù)據(jù)庫與數(shù)據(jù)表都配置在web.config文件中,其設(shè)置如下:

add key="CacheDatabaseName" value="MSPetShop4"/>
add key="CategoryTableDependency" value="Category"/>
add key="ProductTableDependency" value="Product,Category"/>
add key="ItemTableDependency" value="Product,Category,Item"/>

  根據(jù)各個(gè)數(shù)據(jù)表間的依賴關(guān)系,因而不同的數(shù)據(jù)表需要建立的依賴項(xiàng)也是不相同的,從配置文件中的value值可以看出。然而不管建立依賴項(xiàng)的多寡,其創(chuàng)建的行為邏輯都是相似的,因而在設(shè)計(jì)時(shí),抽象了一個(gè)共同的類TableDependency,并通過建立帶參數(shù)的構(gòu)造函數(shù),完成對依賴項(xiàng)的建立。由于接口方法GetDependency()的實(shí)現(xiàn)中,返回的對象dependency是在受保護(hù)的構(gòu)造函數(shù)創(chuàng)建的,因此這里的實(shí)現(xiàn)方式也可以看作是Template Method模式的靈活運(yùn)用。例如TableDependency的子類Product,就是利用父類的構(gòu)造函數(shù)建立了Product、Category數(shù)據(jù)表的SqlCacheDependency依賴:

public class Product : TableDependency
{
 public Product() : base("ProductTableDependency") { }
}

  如果需要自定義CacheDependency,那么創(chuàng)建依賴項(xiàng)的方式又有不同。然而不管是創(chuàng)建SqlCacheDependency對象,還是自定義的CacheDependency對象,都是將這些依賴項(xiàng)添加到AggregateCacheDependency類中,因而我們也可以為自定義CacheDependency建立專門的類,只要實(shí)現(xiàn)IPetShopCacheDependency接口即可。

4.3.3  CacheDependency工廠

  繼承了抽象類TableDependency的Product、Category和Item類均需要在調(diào)用時(shí)創(chuàng)建各自的對象。由于它們的父類TableDependency實(shí)現(xiàn)了接口IPetShopCacheDependency,因而它們也間接實(shí)現(xiàn)了IPetShopCacheDependency接口,這為實(shí)現(xiàn)工廠模式提供了前提。

  在PetShop 4.0中,依然利用了配置文件和反射技術(shù)來實(shí)現(xiàn)工廠模式。命名空間PetShop.CacheDependencyFactory中,類DependencyAccess即為創(chuàng)建IPetShopCacheDependency對象的工廠類:

public static class DependencyAccess
{ 
 public static IPetShopCacheDependency CreateCategoryDependency()
 {
 return LoadInstance("Category");
 }
 public static IPetShopCacheDependency CreateProductDependency()
 {
 return LoadInstance("Product");
 }
 public static IPetShopCacheDependency CreateItemDependency()
 {
 return LoadInstance("Item");
 }
 private static IPetShopCacheDependency LoadInstance(string className)
 {
 string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
 string fullyQualifiedClass = path + "." + className;
 return (IPetShopCacheDependency)Assembly.Load(path).CreateInstance(fullyQualifiedClass);
 }
}

整個(gè)工廠模式的實(shí)現(xiàn)如圖4-3所示:


圖4-3 CacheDependency工廠

  雖然DependencyAccess類創(chuàng)建了實(shí)現(xiàn)了IPetShopCacheDependency接口的類Category、Product、Item,然而我們之所以引入IPetShopCacheDependency接口,其目的就在于獲得創(chuàng)建了依賴項(xiàng)的    AggregateCacheDependency類型的對象。我們可以調(diào)用對象的接口方法GetDependency(),如下所示:

AggregateCacheDependency dependency = DependencyAccess.CreateCategoryDependency().GetDependency();

  為了方便調(diào)用者,似乎我們可以對DependencyAccess類進(jìn)行改進(jìn),將原有的CreateCategoryDependency()方法,修改為創(chuàng)建AggregateCacheDependency類型對象的方法。

  然而這樣的做法擾亂了作為工廠類的DependencyAccess的本身職責(zé),且創(chuàng)建IPetShopCacheDependency接口對象的行為仍然有可能被調(diào)用者調(diào)用,所以保留原有的DependencyAccess類仍然是有必要的。

  在PetShop 4.0的設(shè)計(jì)中,是通過引入Facade模式以方便調(diào)用者更加簡單地獲得AggregateCacheDependency類型對象。

4.3.4  引入Facade模式

  利用Facade模式可以將一些復(fù)雜的邏輯進(jìn)行包裝,以方便調(diào)用者對這些復(fù)雜邏輯的調(diào)用。就好像提供一個(gè)統(tǒng)一的門面一般,將內(nèi)部的子系統(tǒng)封裝起來,統(tǒng)一為一個(gè)高層次的接口。一個(gè)典型的Facade模式示意圖如下所示:


圖4-4 Facade模式

  Facade模式的目的并非要引入一個(gè)新的功能,而是在現(xiàn)有功能的基礎(chǔ)上提供一個(gè)更高層次的抽象,使得調(diào)用者可以直接調(diào)用,而不用關(guān)心內(nèi)部的實(shí)現(xiàn)方式。以CacheDependency工廠為例,我們需要為調(diào)用者提供獲得AggregateCacheDependency對象的簡便方法,因而創(chuàng)建了DependencyFacade類:

public static class DependencyFacade
{
 private static readonly string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
 public static AggregateCacheDependency GetCategoryDependency()
 {
 if (!string.IsNullOrEmpty(path))
  return DependencyAccess.CreateCategoryDependency().GetDependency();
 else
  return null;
 }
 public static AggregateCacheDependency GetProductDependency()
 {
 if (!string.IsNullOrEmpty(path))
  return DependencyAccess.CreateProductDependency().GetDependency();
 else
  return null;
 }
 public static AggregateCacheDependency GetItemDependency()
 {
 if (!string.IsNullOrEmpty(path))
  return DependencyAccess.CreateItemDependency().GetDependency();
 else
  return null;
 }
}

  DependencyFacade類封裝了獲取AggregateCacheDependency類型對象的邏輯,如此一來,調(diào)用者可以調(diào)用相關(guān)方法獲得創(chuàng)建相關(guān)依賴項(xiàng)的AggregateCacheDependency類型對象:

AggregateCacheDependency dependency = DependencyFacade.GetCategoryDependency();

  比起直接調(diào)用DependencyAccess類的GetDependency()方法而言,除了方法更簡單之外,同時(shí)它還對CacheDependencyAssembly配置節(jié)進(jìn)行了判斷,如果其值為空,則返回null對象。

  在PetShop.Web的App_Code文件夾下,靜態(tài)類WebUtility的GetCategoryName()和GetProductName()方法調(diào)用了DependencyFacade類。例如GetCategoryName()方法:

public static string GetCategoryName(string categoryId)
{
 Category category = new Category();
 if (!enableCaching)
  return category.GetCategory(categoryId).Name;

 string cacheKey = string.Format(CATEGORY_NAME_KEY, categoryId);

 // 檢查緩存中是否存在該數(shù)據(jù)項(xiàng);
 string data = (string)HttpRuntime.Cache[cacheKey];
 if (data == null)
 {
  // 通過web.config的配置獲取duration值;
  int cacheDuration = int.Parse(ConfigurationManager.AppSettings["CategoryCacheDuration"]);
  // 如果緩存中不存在該數(shù)據(jù)項(xiàng),則通過業(yè)務(wù)邏輯層訪問數(shù)據(jù)庫獲取;
  data = category.GetCategory(categoryId).Name;
  // 通過Facade類創(chuàng)建AggregateCacheDependency對象;
  AggregateCacheDependency cd = DependencyFacade.GetCategoryDependency();
  // 將數(shù)據(jù)項(xiàng)以及AggregateCacheDependency 對象存儲到緩存中;
  HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
 }
 return data;
}

  GetCategoryName()方法首先會檢查緩存中是否已經(jīng)存在CategoryName數(shù)據(jù)項(xiàng),如果已經(jīng)存在,就通過緩存直接獲取數(shù)據(jù);否則將通過業(yè)務(wù)邏輯層調(diào)用數(shù)據(jù)訪問層訪問數(shù)據(jù)庫獲得CategoryName,在獲得了CategoryName后,會將新獲取的數(shù)據(jù)連同DependencyFacade類創(chuàng)建的AggregateCacheDependency對象添加到緩存中。

  WebUtility靜態(tài)類被表示層的許多頁面所調(diào)用,例如Product頁面:

public partial class Products : System.Web.UI.Page
{
 protected void Page_Load(object sender, EventArgs e)
 {
 Page.Title = WebUtility.GetCategoryName(Request.QueryString["categoryId"]);
 }
}

  顯示頁面title的邏輯是放在Page_Load事件方法中,因而每次打開該頁面都要執(zhí)行獲取CategoryName的方法。如果沒有采用緩存機(jī)制,當(dāng)Category數(shù)據(jù)較多時(shí),頁面的顯示就會非常緩慢。

4.3.5  引入Proxy模式

  業(yè)務(wù)邏輯層BLL中與Product、Category、Item有關(guān)的業(yè)務(wù)方法,其實(shí)現(xiàn)邏輯是調(diào)用數(shù)據(jù)訪問層(DAL)對象訪問數(shù)據(jù)庫,以獲取相關(guān)數(shù)據(jù)。為了改善系統(tǒng)性能,我們就需要為這些實(shí)現(xiàn)方法增加緩存機(jī)制的邏輯。當(dāng)我們操作增加了緩存機(jī)制的業(yè)務(wù)對象時(shí),對于調(diào)用者而言,應(yīng)與BLL業(yè)務(wù)對象的調(diào)用保持一致。也即是說,我們需要引入一個(gè)新的對象去控制原來的BLL業(yè)務(wù)對象,這個(gè)新的對象就是Proxy模式中的代理對象。

  以PetShop.BLL.Product業(yè)務(wù)對象為例,PetShop為其建立了代理對象ProductDataProxy,并在GetProductByCategory()等方法中,引入了緩存機(jī)制,例如:

public static class ProductDataProxy
{

 private static readonly int productTimeout = int.Parse(ConfigurationManager.AppSettings["ProductCacheDuration"]);
 private static readonly bool enableCaching = bool.Parse(ConfigurationManager.AppSettings["EnableCaching"]);
 
 public static IList
GetProductsByCategory(string category)
 {
 Product product = new Product();

 if (!enableCaching)
  return product.GetProductsByCategory(category);

 string key = "product_by_category_" + category;
 IList data = (IList )HttpRuntime.Cache[key];

 // Check if the data exists in the data cache
 if (data == null)
 {
  data = product.GetProductsByCategory(category);

  // Create a AggregateCacheDependency object from the factory
  AggregateCacheDependency cd = DependencyFacade.GetProductDependency();

  // Store the output in the data cache, and Add the necessary AggregateCacheDependency object
  HttpRuntime.Cache.Add(key, data, cd, DateTime.Now.AddHours(productTimeout), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
 }
 return data;
 }
}

  與業(yè)務(wù)邏輯層Product對象的GetProductsByCategory()方法相比,增加了緩存機(jī)制。當(dāng)緩存內(nèi)不存在相關(guān)數(shù)據(jù)項(xiàng)時(shí),則直接調(diào)用業(yè)務(wù)邏輯層Product的GetProductsByCategory()方法來獲取數(shù)據(jù),并將其與對應(yīng)的AggregateCacheDependency對象一起存儲在緩存中。

  引入Proxy模式,實(shí)現(xiàn)了在緩存級別上對業(yè)務(wù)對象的封裝,增強(qiáng)了對業(yè)務(wù)對象的控制。由于暴露在對象外的方法是一致的,因而對于調(diào)用方而言,調(diào)用代理對象與真實(shí)對象并沒有實(shí)質(zhì)的區(qū)別。

  從職責(zé)分離與分層設(shè)計(jì)的角度分析,我更希望這些Proxy對象是被定義在業(yè)務(wù)邏輯層中,而不像在PetShop的設(shè)計(jì)那樣,被劃分到表示層UI中。此外,如果需要考慮程序的可擴(kuò)展性與可替換性,我們還可以為真實(shí)對象與代理對象建立統(tǒng)一的接口或抽象類。然而,單以PetShop的表示層調(diào)用來看,采用靜態(tài)類與靜態(tài)方法的方式,或許更為合理。我們需要謹(jǐn)記,“過度設(shè)計(jì)”是軟件設(shè)計(jì)的警戒線。

  如果需要對UI層采用緩存機(jī)制,將應(yīng)用程序數(shù)據(jù)存放到緩存中,就可以調(diào)用這些代理對象。以ProductsControl用戶控件為例,調(diào)用方式如下:

productsList.DataSource = ProductDataProxy.GetProductsByCategory(categoryKey);

  productsList對象屬于自定義的CustomList類型,這是一個(gè)派生自System.Web.UI.WebControls.DataList控件的類,它的DataSource屬性可以接受IList集合對象。
不過在PetShop 4.0的設(shè)計(jì)中,對于類似于ProductsControl類型的控件而言,采用的緩存機(jī)制是頁輸出緩存。我們可以從ProductsControl.ascx頁面的Source代碼中發(fā)現(xiàn)端倪:

%@ OutputCache Duration="100000" VaryByParam="page;categoryId" %>

  與ASP.NET 1.x的頁輸出緩存不同的是,在ASP.NET 2.0中,為ASP.NET用戶控件新引入了CachePolicy屬性,該屬性的類型為ControlCachePolicy類,它以編程方式實(shí)現(xiàn)了對ASP.NET用戶控件的輸出緩存設(shè)置。我們可以通過設(shè)置ControlCachePolicy類的Dependency屬性,來設(shè)置與該用戶控件相關(guān)的依賴項(xiàng),例如在ProductsControl用戶控件中,進(jìn)行如下的設(shè)置:

protected void Page_Load(object sender, EventArgs e)
{
 this.CachePolicy.Dependency = DependencyFacade.GetProductDependency();
}

  采用頁輸出緩存,并且利用ControlCachePolicy設(shè)置輸出緩存,能夠?qū)I(yè)務(wù)數(shù)據(jù)與整個(gè)頁面放入到緩存中。這種方式比起應(yīng)用程序緩存而言,在性能上有很大的提高。同時(shí),它又通過引入的SqlCacheDependency特性有效地避免了“數(shù)據(jù)過期”的缺點(diǎn),因而在PetShop 4.0中被廣泛采用。相反,之前為Product、Category、Item業(yè)務(wù)對象建立的代理對象則被“投閑散置”,僅僅作為一種設(shè)計(jì)方法的展示而“幸存”與整個(gè)系統(tǒng)的源代碼中。

以上就是PetShop中ASP.NET緩存全部內(nèi)容,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

您可能感興趣的文章:
  • 學(xué)會sql數(shù)據(jù)庫關(guān)系圖(Petshop)
  • 《解剖PetShop》之一:PetShop的系統(tǒng)架構(gòu)設(shè)計(jì)
  • 《解剖PetShop》之二:PetShop數(shù)據(jù)訪問層數(shù)之據(jù)庫訪問設(shè)計(jì)
  • 《解剖PetShop》之三:PetShop數(shù)據(jù)訪問層之消息處理
  • 《解剖PetShop》之五:PetShop之業(yè)務(wù)邏輯層設(shè)計(jì)
  • 《解剖PetShop》之六:PetShop之表示層設(shè)計(jì)

標(biāo)簽:呂梁 泰安 德宏 玉林 房產(chǎn) 喀什 佳木斯

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《《解剖PetShop》之四:PetShop之ASP.NET緩存》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    四川省| 静宁县| 老河口市| 三台县| 独山县| 枣庄市| 三河市| 宁强县| 渝北区| 浮山县| 德令哈市| 扶绥县| 河津市| 万州区| 双辽市| 日喀则市| 鹿泉市| 沈阳市| 鸡西市| 山东省| 普安县| 日喀则市| 凤山市| 苗栗市| 视频| 伊宁市| 城固县| 仁怀市| 巴彦淖尔市| 广安市| 鹰潭市| 莱州市| 巧家县| 宜兴市| 达拉特旗| 庆安县| 阿巴嘎旗| 大悟县| 宜章县| 福泉市| 剑川县|