|
閱讀目錄 許多做過程序性能優(yōu)化的人,或者關(guān)注過程程序性能的人,應(yīng)該都使用過各類緩存技術(shù)。 而我今天所說的Cache是專指ASP.NET的Cache,我們可以使用HttpRuntime.Cache訪問到的那個Cache,而不是其它的緩存技術(shù)。 以前我在【我心目中的Asp.net核心對象】 這篇博客中簡單地提過它,今天我打算為它寫篇專題博客,專門來談?wù)勊?,因為它實在是太重要了。在這篇博客中, 我不僅要介紹它的一些常見用法,還將介紹它的一些高級用法。 在上篇博客【在.net中讀寫config文件的各種方法】 的結(jié)尾處,我給大家留了一個問題,今天,我將在這篇博客中給出一個我認(rèn)為較為完美的答案。 本文提到的【延遲操作】方法(如:延遲合并寫入數(shù)據(jù)庫)屬于我的經(jīng)驗總結(jié),希望大家能喜歡這個思路。 Cache的基本用途提到Cache,不得不說說它的主要功能:改善程序性能。 由于這種動態(tài)頁面技術(shù)通常需要從數(shù)據(jù)源獲取數(shù)據(jù),并經(jīng)過一些計算邏輯,最終變成一些HTML代碼發(fā)給客戶端顯示。而這些計算過程顯然也是有成本的。 這些處理成本最直接可表現(xiàn)為影響服務(wù)器的響應(yīng)速度,尤其是當(dāng)數(shù)據(jù)的處理過程變得復(fù)雜以及訪問量變大時,會變得比較明顯。 另一方面,有些數(shù)據(jù)并非時刻在發(fā)生變化,如果我們可以將一些變化不頻繁的數(shù)據(jù)的最終計算結(jié)果(包括頁面輸出)緩存起來, 就可以非常明顯地提升程序的性能,緩存的最常見且最重要的用途就體現(xiàn)在這個方面。 這也是為什么一說到性能優(yōu)化時,一般都將緩存擺在第一位的原因。 我今天要說到的ASP.NET Cache也是可以實現(xiàn)這種緩存的一種技術(shù)。 不過,它還有其它的一些功能,有些是其它緩存技術(shù)所沒有的。 Cache的定義在介紹Cache的用法前,我們先來看一下Cache的定義:(說明:我忽略了一些意義不大的成員)
// 實現(xiàn)用于 Web 應(yīng)用程序的緩存。無法繼承此類。 public sealed class Cache : IEnumerable { // 用于 Cache.Insert(...) 方法調(diào)用中的 absoluteExpiration 參數(shù)中以指示項從不過期。 public static readonly DateTime NoAbsoluteExpiration; // 用作 Cache.Insert(...) 或 Cache.Add(...) // 方法調(diào)用中的 slidingExpiration 參數(shù),以禁用可調(diào)過期。 public static readonly TimeSpan NoSlidingExpiration; // 獲取或設(shè)置指定鍵處的緩存項。 public object this[string key] { get; set; } // 將指定項添加到 System.Web.Caching.Cache 對象,該對象具有依賴項、過期和優(yōu)先級策略 // 以及一個委托(可用于在從 Cache 移除插入項時通知應(yīng)用程序)。 public object Add(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback); // 從 System.Web.Caching.Cache 對象檢索指定項。 // key: 要檢索的緩存項的標(biāo)識符。 // 返回結(jié)果: 檢索到的緩存項,未找到該鍵時為 null。 public object Get(string key); public void Insert(string key, object value); public void Insert(string key, object value, CacheDependency dependencies); public void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration); // 摘要: // 向 System.Web.Caching.Cache 對象中插入對象,后者具有依賴項、過期和優(yōu)先級策略 // 以及一個委托(可用于在從 Cache 移除插入項時通知應(yīng)用程序)。 // // 參數(shù): // key: // 用于引用該對象的緩存鍵。 // // value: // 要插入緩存中的對象。 // // dependencies: // 該項的文件依賴項或緩存鍵依賴項。當(dāng)任何依賴項更改時,該對象即無效, // 并從緩存中移除。如果沒有依賴項,則此參數(shù)包含 null。 // // absoluteExpiration: // 所插入對象將過期并被從緩存中移除的時間。 // 如果使用絕對過期,則 slidingExpiration 參數(shù)必須為 Cache.NoSlidingExpiration。 // // slidingExpiration: // 最后一次訪問所插入對象時與該對象過期時之間的時間間隔。如果該值等效于 20 分鐘, // 則對象在最后一次被訪問 20 分鐘之后將過期并被從緩存中移除。如果使用可調(diào)過期,則 // absoluteExpiration 參數(shù)必須為 System.Web.Caching.Cache.NoAbsoluteExpiration。 // // priority: // 該對象相對于緩存中存儲的其他項的成本,由 System.Web.Caching.CacheItemPriority 枚舉表示。 // 該值由緩存在退出對象時使用;具有較低成本的對象在具有較高成本的對象之前被從緩存移除。 // // onRemoveCallback: // 在從緩存中移除對象時將調(diào)用的委托(如果提供)。 // 當(dāng)從緩存中刪除應(yīng)用程序的對象時,可使用它來通知應(yīng)用程序。 // // 異常: // System.ArgumentException: // 為要添加到 Cache 中的項設(shè)置 absoluteExpiration 和 slidingExpiration 參數(shù)。 // // System.ArgumentNullException: // key 或 value 參數(shù)為 null。 // // System.ArgumentOutOfRangeException: // 將 slidingExpiration 參數(shù)設(shè)置為小于 TimeSpan.Zero 或大于一年的等效值。 public void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback); // 從應(yīng)用程序的 System.Web.Caching.Cache 對象移除指定項。 public object Remove(string key); // 將對象與依賴項策略、到期策略和優(yōu)先級策略 // 以及可用來在從緩存中移除項【之前】通知應(yīng)用程序的委托一起插入到 Cache 對象中。 // 注意:此方法受以下版本支持:3.5 SP1、3.0 SP1、2.0 SP1 public void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemUpdateCallback onUpdateCallback); } ASP.NET為了方便我們訪問Cache,在HttpRuntime類中加了一個靜態(tài)屬性Cache,這樣,我們就可以在任意地方使用Cache的功能。 而且,ASP.NET還給它增加了二個“快捷方式”:Page.Cache, HttpContext.Cache,我們通過這二個對象也可以訪問到HttpRuntime.Cache, 注意:這三者是在訪問同一個對象。Page.Cache訪問了HttpContext.Cache,而HttpContext.Cache又直接訪問HttpRuntime.Cache Cache常見用法通常,我們使用Cache時,一般只有二個操作:讀,寫。 public object this[string key] { get { return this.Get(key); } set { this.Insert(key, value); } } 可以看到:讀緩存,其實是在調(diào)用Get方法,而寫緩存則是在調(diào)用Insert方法的最簡單的那個重載版本。 注意了:Add方法也可以將一個對象放入緩存,這個方法有7個參數(shù),而Insert也有一個簽名類似的重載版本, 它們有著類似的功能:將指定項添加到 System.Web.Caching.Cache 對象,該對象具有依賴項、過期和優(yōu)先級策略以及一個委托(可用于在從 Cache 移除插入項時通知應(yīng)用程序)。 然而,它們有一點小的區(qū)別:當(dāng)要加入的緩存項已經(jīng)在Cache中存在時,Insert將會覆蓋原有的緩存項目,而Add則不會修改原有緩存項。 也就是說:如果您希望某個緩存項目一旦放入緩存后,就不要再被修改,那么調(diào)用Add確實可以防止后來的修改操作。 而調(diào)用Insert方法,則永遠(yuǎn)會覆蓋已存在項(哪怕以前是調(diào)用Add加入的)。 從另一個角度看,Add的效果更像是 static readonly 的行為,而Insert的效果則像 static 的行為。 由于緩存項可以讓我們隨時訪問,看起來確實有點static成員的味道,但它們有著更高級的特性,比如: 緩存過期(絕對過期,滑動過期),緩存依賴(依賴文件,依賴其它緩存項),移除優(yōu)先級,緩存移除前后的通知等等。 后面我將會分別介紹這四大類特性。 Cache類的特點Cache類有一個很難得的優(yōu)點,用MSDN上的說話就是: 此類型是線程安全的。 為什么這是個難得的優(yōu)點呢?因為在.net中,絕大多數(shù)類在實現(xiàn)時,都只是保證靜態(tài)類型的方法是線程安全,
而不考慮實例方法是線程安全。這也算是一條基本的.NET設(shè)計規(guī)范原則。 此類型的公共靜態(tài)(在 Visual Basic 中為 Shared)成員是線程安全的。但不能保證任何實例成員是線程安全的。 所以,這就意味著我們可以在任何地方讀寫Cache都不用擔(dān)心Cache的數(shù)據(jù)在多線程環(huán)境下的數(shù)據(jù)同步問題。 多線程編程中,最復(fù)雜的問題就是數(shù)據(jù)的同步問題,而Cache已經(jīng)為我們解決了這些問題。 不過我要提醒您:ASP.NET本身就是一個多線程的編程模型,所有的請求是由線程池的線程來處理的。 通常,我們在多線程環(huán)境中為了解決數(shù)據(jù)同步問題,一般是采用鎖來保證數(shù)據(jù)同步, 自然地,ASP.NET也不例外,它為了解決數(shù)據(jù)的同步問題,內(nèi)部也是采用了鎖。 說到這里,或許有些人會想:既然只一個Cache的靜態(tài)實例,那么這種鎖會不會影響并發(fā)? 說明:CacheInternal是個內(nèi)部用的包裝類,Cache的許多操作都要由它來完成。 在上面的代碼中,numSingleCaches的計算過程很重要,如果上面代碼不容易理解,那么請看我下面的示例代碼:
程序?qū)敵觯?/p> 1,2,4,4,8,8,8,8,16,16,16,16,16,16,16,16,32,32,32,32 CacheMultiple的構(gòu)造函數(shù)如下: 現(xiàn)在您應(yīng)該明白了吧:CacheSingle其實是ASP.NET內(nèi)部使用的緩存容器,多個CPU時,它會創(chuàng)建多個緩存容器。 說明:參數(shù)中的hashCode是直接調(diào)用我們傳的key.GetHashCode() ,GetHashCode是由Object類定義的。 所以,從這個角度看,雖然ASP.NET的Cache只有一個HttpRuntime.Cache靜態(tài)成員,但它的內(nèi)部卻可能會包含多個緩存容器, 這種設(shè)計可以在一定程度上減少并發(fā)的影響。 不管如何設(shè)計,在多線程環(huán)境下,共用一個容器,沖突是免不了的。如果您只是希望簡單的緩存一些數(shù)據(jù),
不需要Cache的許多高級特性,那么,可以考慮不用Cache 。
比如:可以創(chuàng)建一個Dictionary或者Hashtable的靜態(tài)實例,它也可以完成一些基本的緩存工作,
不過,我要提醒您:您要自己處理多線程訪問數(shù)據(jù)時的數(shù)據(jù)同步問題。 接下來,我們來看一下Cache的高級特性,這些都是Dictionary或者Hashtable不能完成的。 緩存項的過期時間ASP.NET支持二種緩存項的過期策略:絕對過期和滑動過期。 以上二個選項分別對應(yīng)Add, Insert方法中的DateTime absoluteExpiration, TimeSpan slidingExpiration這二個參數(shù)。 這二個參數(shù)比較簡單,我就不多說了,只說一句:如果都使用Noxxxxx這二個選項,那么緩存項就一直保存在緩存中。(或許也會被移除) 緩存項的依賴關(guān)系 - 依賴其它緩存項ASP.NET Cache有個很強(qiáng)大的功能,那就是緩存依賴。一個緩存項可以依賴于另一個緩存項。
以下示例代碼創(chuàng)建了二個緩存項,且它們間有依賴關(guān)系。首先請看頁面代碼:
當(dāng)運行這個示例頁面時,運行結(jié)果如下圖所示, 點擊按鈕【設(shè)置Key1的值】時,將會出現(xiàn)緩存項的內(nèi)容(左圖)。點擊按鈕【設(shè)置Key2的值】時,此時將獲取不到緩存項的內(nèi)容(右圖)。
根據(jù)結(jié)果并分析代碼,我們可以看出,在創(chuàng)建Key1的緩存項時,我們使用了這種緩存依賴關(guān)系: CacheDependency dep = new CacheDependency(null, new string[] { "key2" }); 所以,當(dāng)我們更新Key2的緩存項時,Key1的緩存就失效了(不存在)。 不要小看了這個示例。的確,僅看這幾行示例代碼,或許它們實在是沒有什么意義。 那么,我就舉個實際的使用場景來說明它的使用價值。
上面這幅圖是我寫的一個小工具。在示意圖中,左下角是一個緩存表CacheTable,它由一個叫Table1BLL的類來維護(hù)。 CacheTable的數(shù)據(jù)來源于Table1,由Table1.aspx頁面顯示出來。 同時,ReportA, ReportB的數(shù)據(jù)也主要來源于Table1,由于Table1的訪問幾乎絕大多數(shù)都是讀多寫少,所以,我將Table1的數(shù)據(jù)緩存起來了。 而且,ReportA, ReportB這二個報表采用GDI直接畫出(由報表模塊生成,可認(rèn)是Table1BLL的上層類),鑒于這二個報表的瀏覽次數(shù)較多且數(shù)據(jù)源是讀多寫少, 因此,這二個報表的輸出結(jié)果,我也將它們緩存起來。 在這個場景中,我們可以想像一下:如果希望在Table1的數(shù)據(jù)發(fā)生修改后,如何讓二個報表的緩存結(jié)果失效? 幸好,ASP.NET Cache支持一種叫做緩存依賴的特性,我們只需要讓Table1BLL公開它緩存CacheTable的KEY就可以了(假設(shè)KEY為 CacheTableKey), 然后,其它的緩存結(jié)果如果要基于CacheTable,設(shè)置一下對【CacheTableKey】的依賴就可以實現(xiàn)這樣的效果: 當(dāng)CacheTable更新后,被依賴的緩存結(jié)果將會自動清除。這樣就徹底地解決了模塊間的緩存數(shù)據(jù)依賴問題。 緩存項的依賴關(guān)系 - 文件依賴在上篇博客【在.net中讀寫config文件的各種方法】的結(jié)尾,
我給大家留了一個問題: 首先,我要說明一點:上次博客的問題,雖然解決方案與Cache的文件依賴有關(guān),但還需與緩存的移除通知配合使用才能完美的解決問題。 為了便于內(nèi)容的安排,我先使用Cache的文件依賴來簡單的實現(xiàn)一個粗糙的版本,在本文的后續(xù)部分再來完善這個實現(xiàn)。 先來看個粗糙的版本。假如我的網(wǎng)站中有這樣一個配置參數(shù)類型:
我可以將它配置在這樣一個XML文件中: <?xml version="1.0" encoding="utf-8"?> <RunOptions xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:xsd="http://www./2001/XMLSchema"> <WebSiteUrl>http://www.cnblogs.com/fish-li</WebSiteUrl> <UserName>fish li</UserName> </RunOptions> 下面的代碼就可以實現(xiàn):在XML修改后,瀏覽頁面就能立即看到最新的參數(shù)值:
注意:這里仍然是在使用CacheDependency,只是我們現(xiàn)在是給它的構(gòu)造函數(shù)的第一個參數(shù)傳遞要依賴的文件名。 在即將結(jié)束對緩存的依賴介紹之前,還要補充二點: 緩存項的移除優(yōu)先級緩存的做法有很多種,一個靜態(tài)變量也可以稱為是一個緩存。一個靜態(tài)的集合就是一個緩存的容器了。 我想很多人都用Dictionary,List,或者Hashtable做過緩存容器,我們可以使用它們來保存各種數(shù)據(jù),改善程序的性能。 一般情況下,如果我們直接使用這類集合去緩存各類數(shù)據(jù),那么,那些數(shù)據(jù)所占用的內(nèi)存將不會被回收,哪怕它們的使用機(jī)會并不是很多。 當(dāng)緩存數(shù)據(jù)越來越多時,它們所消耗的內(nèi)存自然也會越來越多。那么,能不能在內(nèi)存不充足時,釋放掉一些訪問不頻繁的緩存項呢? 這個問題也確實是個較現(xiàn)實的問題。雖然,使用緩存會使用程序運行更快,但是,我們數(shù)據(jù)會無限大,不可能統(tǒng)統(tǒng)緩存起來, 畢竟,內(nèi)存空間是有限的。因此,我們可以使用前面所說的基于一段時間內(nèi)不再訪問就刪除的策略來解決這個問題。 然而,在我們編碼時,根本不知道我們的程序會運行在什么配置標(biāo)準(zhǔn)的計算機(jī)上,因此,根本不可能會對內(nèi)存的大小作出任何假設(shè), 此時,我們可能會希望當(dāng)緩存占用過多的內(nèi)存時,且當(dāng)內(nèi)存不夠時,能自動移除一些不太重要的緩存項,這或許也比較有意義。 對于這個需求,在.net framework提供了二種解決辦法,一種是使用WeakReference類,另一種是使用Cache 。
不過,既然我們是在使用ASP.NET,選擇Cache當(dāng)然會更方便。
在Cache的Add, Insert方法的某些重載版本中,可以指定緩存項的保存優(yōu)先級策略,由參數(shù)CacheItemPriority priority來傳入。
其中,CacheItemPriority是一個枚舉類型,它包含了如下枚舉值:
說明:當(dāng)我們調(diào)用Cache的Add, Insert方法時,如果不指定CacheItemPriority選項,最終使用Normal所代表的優(yōu)先級。 如果我們希望將某個可能不太重要的數(shù)據(jù)放入緩存時,可以指定優(yōu)先級為Low或者BelowNormal。 如果想讓緩存項在內(nèi)存不足時,也不會被移除(除非到期或者依賴項有改變),可使用NotRemovable。 顯然,我們可以使用這個特性來控制緩存對內(nèi)存壓力的影響。 其它的緩存方案,如static Collection + WeakReference也較難實現(xiàn)這樣靈活的控制。 緩存項的移除通知ASP.NET Cache與一些static變量所實現(xiàn)的緩存效果并不相同,它的緩存項是可以根據(jù)一些特定的條件失效的,那些失效的緩存將會從內(nèi)存中移除。 雖然,某些移除條件并不是由我們的代碼直接解發(fā)的,但ASP.NET還是提供一種方法讓我們可以在緩存項在移除時,能通知我們的代碼。 注意哦:ASP.NET Cache支持移除【前】通知 和 移除【后】通知二種通知方式。 我們可以在調(diào)用Add, Insert方法時,通過參數(shù)onRemoveCallback傳遞一個CacheItemRemovedCallback類型的委托,以便在移除指定的緩存項時,
能夠通知我們。這個委托的定義如下:
委托的各個參數(shù)的含義以及移除原因,在注釋中都有明確的解釋,我也不再重復(fù)了。 通常,我們會以下面這種方式從Cache中獲取結(jié)果: RunOptions options = HttpRuntime.Cache[RunOptionsCacheKey] as RunOptions; if( options == null ) { // 緩存中沒有,則從文件中加載 // .................................. HttpRuntime.Cache.Insert(RunOptionsCacheKey, options, dep); } return options; 這其實也是一個慣用法了:先嘗試從緩存中獲取,如果沒有,則從數(shù)據(jù)源中加載,并再次放入緩存。 為什么會在訪問Cache時返回null呢?答案無非就是二種原因:1. 根本沒有放入Cache,2. 緩存項失效被移除了。 為了解決這個問題,微軟在.net framework的3.5 SP1、3.0 SP1、2.0 SP1版本中,加入了【移除前通知】功能,不過,這個方法僅受Insert支持,
隨之而來的還有一個委托和一個移除原因的枚舉定義:
注意:CacheItemUpdateReason這個枚舉只有二項。原因請看MSDN的解釋: 與 CacheItemRemovedReason 枚舉不同,此枚舉不包含 Removed 或 Underused 值??筛碌木彺骓検遣豢梢瞥?,因而絕不會被 ASP.NET 自動移除,即使需要釋放內(nèi)存也是如此。 再一次提醒:有時我們確實需要緩存失效這個特性,但是,緩存失效后會被移除。 雖然我們可以讓后續(xù)的請求在獲取不到緩存數(shù)據(jù)時,從數(shù)據(jù)源中加載,也可以在CacheItemRemovedCallback回調(diào)委托中, 重新加載緩存數(shù)據(jù)到Cache中,但是在數(shù)據(jù)的加載過程中,Cache并不包含我們所期望的緩存數(shù)據(jù),如果加載時間越長,這種【空缺】效果也會越明顯。 這樣會影響(后續(xù)的)其它請求的訪問。為了保證讓我們所期望的緩存數(shù)據(jù)能夠一直存在于Cahce中,且仍有失效機(jī)制,我們可以使用【移除前通知】功能。 巧用緩存項的移除通知 實現(xiàn)【延遲操作】我看過一些ASP.NET的書,也看過一些人寫的關(guān)于Cache方面的文章,基本上,要么是一帶而過,要么只是舉個毫無實際意義的示例。 可惜啊,這么強(qiáng)大的特性,我很少見到有人把它用起來。 今天,我就舉個有實際意義的示例,再現(xiàn)Cache的強(qiáng)大功能! 我有這樣一個頁面,可以讓用戶調(diào)整(上下移動)某個項目分支記錄的上線順序:
當(dāng)用戶需要調(diào)整某條記錄的位置時,頁面會彈出一個對話框,要求輸入一個調(diào)整原因,并會發(fā)郵件通知所有相關(guān)人員。
由于界面的限制,一次操作(點擊上下鍵頭)只是將一條記錄移動一個位置,當(dāng)要對某條記錄執(zhí)行跨越多行移動時,必須進(jìn)行多次移動。 考慮到操作的方便性以及不受重復(fù)郵件的影響,程序需要實現(xiàn)這樣一個需求: 頁面只要求輸入一次原因便可以對一條記錄執(zhí)行多次移動操作,并且不要多次發(fā)重復(fù)郵件,而且要求將最后的移動結(jié)果在郵件中發(fā)出來。 這個需求很合理,畢竟誰都希望操作簡單。 那么如何實現(xiàn)這個需求呢?這里要從二個方面來實現(xiàn),首先,在頁面上我們應(yīng)該要完成這個功能,對一條記錄只彈一次對話框。 由于頁面與服務(wù)端的交互全部采用Ajax方式進(jìn)行(不刷新),狀態(tài)可以采用JS變量來維持,所以這個功能在頁面中是很容易實現(xiàn)。 再來看一下服務(wù)端,由于服務(wù)端并沒有任何狀態(tài),當(dāng)然也可以由頁面把它的狀態(tài)傳給服務(wù)端,但是,哪次操作是最后一次呢? 顯然,這是無法知道的,最后只能修改需求,如果用戶在2分鐘之內(nèi)不再操作某條記錄時,便將最近一次操作視為最后一次操作。 基于新的需求,程序必須記錄用戶的最近一次操作,以便在2分鐘不操作后,發(fā)出一次郵件,但要包含第一次輸入的原因, 還應(yīng)包含最后的修改結(jié)果哦。 該怎么實現(xiàn)這個需求呢? 我立即就想到了ASP.NET Cache,因為我了解它,知道它能幫我完成這個功能。下面我來說說在服務(wù)端是如何實現(xiàn)的。 整個實現(xiàn)的思路是: 為了便于理解,我特意為大家準(zhǔn)備了一個示例。整個示例由三部分組成:一個頁面,一個JS文件,服務(wù)端代碼。先來看頁面代碼:
頁面的顯示效果如下:
說明:在服務(wù)端,我使用了我在【用Asp.net寫自己的服務(wù)框架】那篇博客中提供的服務(wù)框架,
服務(wù)端的全部代碼是這個樣子的:(注意代碼中的注釋)
為了能讓JavaScript能直接調(diào)用C#中的方法,還需要在web.config中加入如下配置: <httpHandlers> <add path="*.fish" verb="*" validate="false" type="MySimpleServiceFramework.AjaxServiceHandler"/> </httpHandlers> 好了,示例代碼就是這些。如果您有興趣,可以在本文的結(jié)尾處下載這些示例代碼,自己親自感受一下利用Cache實現(xiàn)的【延遲處理】的功能。 其實這種【延遲處理】的功能是很有用的,比如還有一種適用場景:有些數(shù)據(jù)記錄可能需要頻繁更新,如果每次更新都去寫數(shù)據(jù)庫,肯定會對數(shù)據(jù)庫造成一定的壓力, 但由于這些數(shù)據(jù)也不是特別重要,因此,我們可以利用這種【延遲處理】來將寫數(shù)據(jù)庫的時機(jī)進(jìn)行合并處理, 最終我們可以實現(xiàn):將多次的寫入變成一次或者少量的寫入操作,我稱這樣效果為:延遲合并寫入 這里我就對數(shù)據(jù)庫的延遲合并寫入提供一個思路:將需要寫入的數(shù)據(jù)記錄放入Cache,調(diào)用Insert方法并提供slidingExpiration和onRemoveCallback參數(shù), 然后在CacheItemRemovedCallback回調(diào)委托中,模仿我前面的示例代碼,將多次變成一次。不過,這樣可能會有一個問題:如果數(shù)據(jù)是一直在修改,那么就一直不會寫入數(shù)據(jù)庫。 最后如果網(wǎng)站重啟了,數(shù)據(jù)可能會丟失。如果擔(dān)心這個問題,那么,可以在回調(diào)委托中,遇到CacheItemRemovedReason.Removed時,使用計數(shù)累加的方式,當(dāng)?shù)竭_(dá)一定數(shù)量后, 再寫入數(shù)據(jù)庫。比如:遇到10次CacheItemRemovedReason.Removed我就寫一次數(shù)據(jù)庫,這樣就會將原來需要寫10次的數(shù)據(jù)庫操作變成一次了。 當(dāng)然了,如果是其它移除原因,寫數(shù)據(jù)庫總是必要的。注意:對于金額這類敏感的數(shù)據(jù),絕對不要使用這種方法。 再補充二點: 關(guān)于緩存的失效時間,我要再提醒一點:通過absoluteExpiration, slidingExpiration參數(shù)所傳入的時間,當(dāng)緩存時間生效時,緩存對象并不會立即移除, ASP.NET Cache大約以20秒的頻率去檢查這些已過時的緩存項。 巧用緩存項的移除通知 實現(xiàn)【自動加載配置文件】在本文的前部分的【文件依賴】小節(jié)中,有一個示例演示了:當(dāng)配置文件更新后,頁面可以顯示最新的修改結(jié)果。
在那個示例中,為了簡單,我直接將配置參數(shù)放在Cache中,每次使用時再從Cache中獲取。
如果配置參數(shù)較多,這種做法或許也會影響性能,畢竟配置參數(shù)并不會經(jīng)常修改,如果能直接訪問一個靜態(tài)變量就能獲取到,應(yīng)該會更快。
通常,我們可能會這樣做:
但是,這種做法有一缺點就是:不能在配置文件更新后,自動加載最新的配置結(jié)果。 為了解決這個問題,我們可以使用Cache提供的文件依賴以及移除通知功能。
前面的示例演示了移除后通知功能,這里我再演示一下移除前通知功能。 下面的代碼演示了在配置文件修改后,自動更新運行參數(shù)的實現(xiàn)方式:(注意代碼中的注釋)
改動很小,只是LoadRunOptions方法做了修改了而已,但是效果卻很酷。 還記得我在上篇博客【在.net中讀寫config文件的各種方法】的結(jié)尾處留下來的問題嗎? 這個示例就是我的解決方案。 文件監(jiān)視技術(shù)的選擇對于文件監(jiān)視,我想有人或許會想到FileSystemWatcher。正好我就來說說關(guān)于【文件監(jiān)視技術(shù)】的選擇問題。 這個組件,早在做WinForm開發(fā)時就用過了,對它也是印象比較深的。
說明:圖片中顯示了發(fā)生過二次事件,但我只是在修改了文件后,做了一次保存操作而已。
本文的結(jié)尾處有我的示例程序,您可以自己去試一下。這里為了方便,還是貼出相關(guān)代碼:
對于這個類的使用,只想說一點:會引發(fā)的事件很多,因此一定要注意過濾。以下引用MSDN的一段說明: Windows 操作系統(tǒng)在 FileSystemWatcher 創(chuàng)建的緩沖區(qū)中通知組件文件發(fā)生更改。如果短時間內(nèi)有很多更改,則緩沖區(qū)可能會溢出。這將導(dǎo)致組件失去對目錄更改的跟蹤,并且它將只提供一般性通知。使用 InternalBufferSize 屬性來增加緩沖區(qū)大小的開銷較大,因為它來自無法換出到磁盤的非頁面內(nèi)存,所以應(yīng)確保緩沖區(qū)大小適中(盡量小,但也要有足夠大小以便不會丟失任何文件更改事件)。若要避免緩沖區(qū)溢出,請使用 NotifyFilter 和 IncludeSubdirectories 屬性,以便可以篩選掉不想要的更改通知。 幸運的是,ASP.NET Cache并沒有使用這個組件,我們不用擔(dān)心文件依賴而引發(fā)的重復(fù)操作問題。 它直接依賴于webengine.dll所提供的API,因此,建議在ASP.NET應(yīng)用程序中,優(yōu)先使用Cache所提供的文件依賴功能。 各種緩存方案的共存ASP.NET Cache是一種緩存技術(shù),然而,我們在ASP.NET程序中還可以使用其它的緩存技術(shù), 這些不同的緩存也各有各自的長處。由于ASP.NET Cache不能提供對外訪問能力,因此,它不可能取代以memcached為代表的分布式緩存技術(shù), 但它由于是不需要跨進(jìn)程訪問,效率也比分布式緩存的速度更快。如果將ASP.NET Cache設(shè)計成【一級緩存】, 分布式緩存設(shè)計成【二級緩存】,就像CPU的緩存那樣,那么將能同時利用二者的所有的優(yōu)點,實現(xiàn)更完美的功能以及速度。 其實緩存是沒有一個明確定義的技術(shù),一個static變量也是一個緩存,一個static集合就是一個緩存容器了。 這種緩存與ASP.NET Cache相比起來,顯然static變量的訪問速度會更快,如果static集合不是設(shè)計得很差的話, 并發(fā)的沖突也可能會比ASP.NET Cache小,也正是因為這一點,static集合也有著廣泛的使用。 然而,ASP.NET Cache的一些高級功能,如:過期時間,緩存依賴(包含文件依賴),移除通知,也是static集合不具備的。 因此,合理地同時使用它們,會讓程序有著最好的性能,也同時擁有更強(qiáng)大的功能。
如果,您認(rèn)為閱讀這篇博客讓您有些收獲,不妨點擊一下右下角的【推薦】按鈕。 感謝您的閱讀,如果您對我的博客所講述的內(nèi)容有興趣,請繼續(xù)關(guān)注我的后續(xù)博客,我是Fish Li 。 |
|
|