序言
在很多訪問量較大的系統中,尤其在某一項數據訪問頻次較高時,我們會考慮使用緩存,減少系統和數據庫的交互,以達到良好的用戶體驗。緩存主要有頁面緩存和數據緩存。數據緩存的實現有很多方式,有基於memcached的,還有基於.net 4.0數據緩存框架,還有一些其他的實現方式。院子里有 PetterLiumemcached快遞上手之C#,有興趣的可以查看,本文主要討論的是基於.net 4.0 數據緩存框架.
數據緩存的實現原理
nopCommerce項目中有兩類的數據緩存,一個是全局數據緩存MemoryCacheManager,是用.net 4.0數據緩存框架實現的。另一個是頁面請求級的數據緩存PerRequestCacheManager是基於HttpContextBase實現的。
1、數據緩存框架是.net 4.0框架中新增的功能,詳細了解.net 4.0 的緩存功能請看阿不寫的全面認識一下.NET 4.0的緩存功能。
圖1 部分緩存框架相關的類
2、基於HttpContextBase頁面請求級數據緩存
HttpContextBase 類為抽象類,該類包含的成員與 HttpContext 類相同。 使用 HttpContextBase 類可以創建一些派生類,這些派生類與
HttpContext 類相似,但是可以進行自定義並在 ASP.NET 管道外部使用。 在執行單元測試時,通常使用派生類實現具有自定義行為的成員以實現正在測試的方案,這更容易進行單元測試。HttpContextWrapper 類是從 HttpContextBase 類派生的。 HttpContextWrapper 類用作 HttpContext 類的包裝。 在運行時,通常使用 HttpContextWrapper 類的實例調用 HttpContext 對象上的成員。
HttpContext的Items集合是IDictionary鍵/值對的對象集合,在HttpRequest的生存期中共享。存儲成本很高的調用的結果,防止該調用在頁面上出現多次。一個HttpRequest中的各個單元需要處理相同或類似的數據。如果數據的生存期只是一個請求,就可以考慮使用HttpContext. Items作為短期的高速緩存。
nopCommerce項目中的緩存
1、緩存的實現
nopCommerce項目緩存類層級圖
ICacheManager接口,該接口定義了數據緩存常用的方法。

2 {
3 /// <summary>
4 /// Gets or sets the value associated with the specified key.
5 /// </summary>
6 /// <typeparam name="T"> Type </typeparam>
7 /// <param name="key"> The key of the value to get. </param>
8 /// <returns> The value associated with the specified key. </returns>
9 T Get<T>( string key);
10
11 /// <summary>
12 /// Adds the specified key and object to the cache.
13 /// </summary>
14 /// <param name="key"> key </param>
15 /// <param name="data"> Data </param>
16 /// <param name="cacheTime"> Cache time </param>
17 void Set( string key, object data, int cacheTime);
18
19 /// <summary>
20 /// Gets a value indicating whether the value associated with the specified key is cached
21 /// </summary>
22 /// <param name="key"> key </param>
23 /// <returns> Result </returns>
24 bool IsSet( string key);
25
26 /// <summary>
27 /// Removes the value with the specified key from the cache
28 /// </summary>
29 /// <param name="key"> /key </param>
30 void Remove( string key);
31
32 /// <summary>
33 /// Removes items by pattern
34 /// </summary>
35 /// <param name="pattern"> pattern </param>
36 void RemoveByPattern( string pattern);
37
38 /// <summary>
39 /// Clear all cache data
40 /// </summary>
41 void Clear();
42 }
CacheExtensions擴展方法對ICacheManager進行擴展。

2 /// Extensions
3 /// </summary>
4 public static class CacheExtensions
5 {
6 public static T Get<T>( this ICacheManager cacheManager, string key, Func<T> acquire)
7 {
8 return Get(cacheManager, key, 60, acquire);
9 }
10
11 public static T Get<T>( this ICacheManager cacheManager, string key, int cacheTime, Func<T> acquire)
12 {
13 if (cacheManager.IsSet(key))
14 {
15 return cacheManager.Get<T>(key);
16 }
17 else
18 {
19 var result = acquire();
20 // if (result != null)
21 cacheManager.Set(key, result, cacheTime);
22 return result;
23 }
24 }
25 }
MemoryCacheCache類,使用.net 緩存框架實現數據緩存

2 /// Represents a MemoryCacheCache
3 /// </summary>
4 public partial class MemoryCacheManager : ICacheManager
5 {
6 protected ObjectCache Cache
7 {
8 get
9 {
10 return MemoryCache.Default;
11 }
12 }
13
14 /// <summary>
15 /// Gets or sets the value associated with the specified key.
16 /// </summary>
17 /// <typeparam name="T"> Type </typeparam>
18 /// <param name="key"> The key of the value to get. </param>
19 /// <returns> The value associated with the specified key. </returns>
20 public T Get<T>( string key)
21 {
22 return (T)Cache[key];
23 }
24
25 /// <summary>
26 /// Adds the specified key and object to the cache.
27 /// </summary>
28 /// <param name="key"> key </param>
29 /// <param name="data"> Data </param>
30 /// <param name="cacheTime"> Cache time </param>
31 public void Set( string key, object data, int cacheTime)
32 {
33 if (data == null)
34 return;
35
36 var policy = new CacheItemPolicy();
37 policy.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime);
38 Cache.Add( new CacheItem(key, data), policy);
39 }
40
41 /// <summary>
42 /// Gets a value indicating whether the value associated with the specified key is cached
43 /// </summary>
44 /// <param name="key"> key </param>
45 /// <returns> Result </returns>
46 public bool IsSet( string key)
47 {
48 return (Cache.Contains(key));
49 }
50
51 /// <summary>
52 /// Removes the value with the specified key from the cache
53 /// </summary>
54 /// <param name="key"> /key </param>
55 public void Remove( string key)
56 {
57 Cache.Remove(key);
58 }
59
60 /// <summary>
61 /// Removes items by pattern
62 /// </summary>
63 /// <param name="pattern"> pattern </param>
64 public void RemoveByPattern( string pattern)
65 {
66 var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
67 var keysToRemove = new List<String>();
68
69 foreach ( var item in Cache)
70 if (regex.IsMatch(item.Key))
71 keysToRemove.Add(item.Key);
72
73 foreach ( string key in keysToRemove)
74 {
75 Remove(key);
76 }
77 }
78
79 /// <summary>
80 /// Clear all cache data
81 /// </summary>
82 public void Clear()
83 {
84 foreach ( var item in Cache)
85 Remove(item.Key);
86 }
87 }
PerRequestCacheManager類,實現頁面請求級的數據緩存。

2 /// Represents a NopStaticCache
3 /// </summary>
4 public partial class PerRequestCacheManager : ICacheManager
5 {
6 private readonly HttpContextBase _context;
7
8 /// <summary>
9 /// Ctor
10 /// </summary>
11 /// <param name="context"> Context </param>
12 public PerRequestCacheManager(HttpContextBase context)
13 {
14 this._context = context;
15 }
16
17 /// <summary>
18 /// Creates a new instance of the NopRequestCache class
19 /// </summary>
20 protected IDictionary GetItems()
21 {
22 if (_context != null)
23 return _context.Items;
24
25 return null;
26 }
27
28 /// <summary>
29 /// Gets or sets the value associated with the specified key.
30 /// </summary>
31 /// <typeparam name="T"> Type </typeparam>
32 /// <param name="key"> The key of the value to get. </param>
33 /// <returns> The value associated with the specified key. </returns>
34 public T Get<T>( string key)
35 {
36 var items = GetItems();
37 if (items == null)
38 return default(T);
39
40 return (T)items[key];
41 }
42
43 /// <summary>
44 /// Adds the specified key and object to the cache.
45 /// </summary>
46 /// <param name="key"> key </param>
47 /// <param name="data"> Data </param>
48 /// <param name="cacheTime"> Cache time </param>
49 public void Set( string key, object data, int cacheTime)
50 {
51 var items = GetItems();
52 if (items == null)
53 return;
54
55 if (data != null)
56 {
57 if (items.Contains(key))
58 items[key] = data;
59 else
60 items.Add(key, data);
61 }
62 }
63
64 /// <summary>
65 /// Gets a value indicating whether the value associated with the specified key is cached
66 /// </summary>
67 /// <param name="key"> key </param>
68 /// <returns> Result </returns>
69 public bool IsSet( string key)
70 {
71 var items = GetItems();
72 if (items == null)
73 return false;
74
75 return (items[key] != null);
76 }
77
78 /// <summary>
79 /// Removes the value with the specified key from the cache
80 /// </summary>
81 /// <param name="key"> /key </param>
82 public void Remove( string key)
83 {
84 var items = GetItems();
85 if (items == null)
86 return;
87
88 items.Remove(key);
89 }
90
91 /// <summary>
92 /// Removes items by pattern
93 /// </summary>
94 /// <param name="pattern"> pattern </param>
95 public void RemoveByPattern( string pattern)
96 {
97 var items = GetItems();
98 if (items == null)
99 return;
100
101 var enumerator = items.GetEnumerator();
102 var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
103 var keysToRemove = new List<String>();
104 while (enumerator.MoveNext())
105 {
106 if (regex.IsMatch(enumerator.Key.ToString()))
107 {
108 keysToRemove.Add(enumerator.Key.ToString());
109 }
110 }
111
112 foreach ( string key in keysToRemove)
113 {
114 items.Remove(key);
115 }
116 }
117
118 /// <summary>
119 /// Clear all cache data
120 /// </summary>
121 public void Clear()
122 {
123 var items = GetItems();
124 if (items == null)
125 return;
126
127 var enumerator = items.GetEnumerator();
128 var keysToRemove = new List<String>();
129 while (enumerator.MoveNext())
130 {
131 keysToRemove.Add(enumerator.Key.ToString());
132 }
133
134 foreach ( string key in keysToRemove)
135 {
136 items.Remove(key);
137 }
138 }
139 }
NopNullCache類,空的數據緩存類。

2 /// Represents a NopNullCache
3 /// </summary>
4 public partial class NopNullCache : ICacheManager
5 {
6 /// <summary>
7 /// Gets or sets the value associated with the specified key.
8 /// </summary>
9 /// <typeparam name="T"> Type </typeparam>
10 /// <param name="key"> The key of the value to get. </param>
11 /// <returns> The value associated with the specified key. </returns>
12 public T Get<T>( string key)
13 {
14 return default(T);
15 }
16
17 /// <summary>
18 /// Adds the specified key and object to the cache.
19 /// </summary>
20 /// <param name="key"> key </param>
21 /// <param name="data"> Data </param>
22 /// <param name="cacheTime"> Cache time </param>
23 public void Set( string key, object data, int cacheTime)
24 {
25 }
26
27 /// <summary>
28 /// Gets a value indicating whether the value associated with the specified key is cached
29 /// </summary>
30 /// <param name="key"> key </param>
31 /// <returns> Result </returns>
32 public bool IsSet( string key)
33 {
34 return false;
35 }
36
37 /// <summary>
38 /// Removes the value with the specified key from the cache
39 /// </summary>
40 /// <param name="key"> /key </param>
41 public void Remove( string key)
42 {
43 }
44
45 /// <summary>
46 /// Removes items by pattern
47 /// </summary>
48 /// <param name="pattern"> pattern </param>
49 public void RemoveByPattern( string pattern)
50 {
51 }
52
53 /// <summary>
54 /// Clear all cache data
55 /// </summary>
56 public void Clear()
57 {
58 }
59 }
2、緩存的應用
下面是BlogService類中的CRUD,從中我們可以了解到,數據緩存是如何處理的,在數據檢索時,直接從緩存取數據,其他方法均根據相關正則表達式移除BlogPost的所有緩存,以避免讀取到臟數據。

2 /// Gets a blog post
3 /// </summary>
4 /// <param name="blogPostId"> Blog post identifier </param>
5 /// <returns> Blog post </returns>
6 public virtual BlogPost GetBlogPostById( int blogPostId)
7 {
8 if (blogPostId == 0)
9 return null;
10
11 string key = string.Format(BLOGPOST_BY_ID_KEY, blogPostId);
12 return _cacheManager.Get(key, () =>
13 {
14 var pv = _blogPostRepository.GetById(blogPostId);
15 return pv;
16 });
17 }
18
19 /// <summary>
20 /// Deletes a blog post
21 /// </summary>
22 /// <param name="blogPost"> Blog post </param>
23 public virtual void DeleteBlogPost(BlogPost blogPost)
24 {
25 if (blogPost == null)
26 throw new ArgumentNullException( " blogPost ");
27
28 _blogPostRepository.Delete(blogPost);
29
30 _cacheManager.RemoveByPattern(BLOGPOST_PATTERN_KEY);
31
32 // event notification
33 _eventPublisher.EntityDeleted(blogPost);
34 }
35
36
37 /// <summary>
38 /// Inserts an blog post
39 /// </summary>
40 /// <param name="blogPost"> Blog post </param>
41 public virtual void InsertBlogPost(BlogPost blogPost)
42 {
43 if (blogPost == null)
44 throw new ArgumentNullException( " blogPost ");
45
46 _blogPostRepository.Insert(blogPost);
47
48 _cacheManager.RemoveByPattern(BLOGPOST_PATTERN_KEY);
49
50 // event notification
51 _eventPublisher.EntityInserted(blogPost);
52 }
53
54 /// <summary>
55 /// Updates the blog post
56 /// </summary>
57 /// <param name="blogPost"> Blog post </param>
58 public virtual void UpdateBlogPost(BlogPost blogPost)
59 {
60 if (blogPost == null)
61 throw new ArgumentNullException( " blogPost ");
62
63 _blogPostRepository.Update(blogPost);
64
65 _cacheManager.RemoveByPattern(BLOGPOST_PATTERN_KEY);
66
67 // event notification
68 _eventPublisher.EntityUpdated(blogPost);
69 }
下面是nopCommerce中該部分的依賴注入部分:ps:nopCommerce的依賴注入會在以后為大家介紹:)

2 builder.Register(c =>
3 // register FakeHttpContext when HttpContext is not available
4 HttpContext.Current != null ?
5 ( new HttpContextWrapper(HttpContext.Current) as HttpContextBase) :
6 ( new FakeHttpContext( " ~/ ") as HttpContextBase))
7 .As<HttpContextBase>()
8 .InstancePerHttpRequest();
9 builder.Register(c => c.Resolve<HttpContextBase>().Request)
10 .As<HttpRequestBase>()
11 .InstancePerHttpRequest();
12 builder.Register(c => c.Resolve<HttpContextBase>().Response)
13 .As<HttpResponseBase>()
14 .InstancePerHttpRequest();
15 builder.Register(c => c.Resolve<HttpContextBase>().Server)
16 .As<HttpServerUtilityBase>()
17 .InstancePerHttpRequest();
18 builder.Register(c => c.Resolve<HttpContextBase>().Session)
19 .As<HttpSessionStateBase>()
20 .InstancePerHttpRequest();
21 // cache manager
22 builder.RegisterType<MemoryCacheManager>().As<ICacheManager>().Named<ICacheManager>( " nop_cache_static ").SingleInstance();
23 builder.RegisterType<PerRequestCacheManager>().As<ICacheManager>().Named<ICacheManager>( " nop_cache_per_request ").InstancePerHttpRequest();
有何改進指出?
在緩存具體實現的時候,除了檢索方法,其他的CRUD方法,均刪除了所有同類的數據緩存,我們是不是可以這樣想,上面的BlogPost肯定是有主鍵的,我們可以根據主鍵對緩存里面數據進行相關的操作,而不是在增刪改的時候,移除所有的BlogPost緩存。
總結
在我們的系統中,根據需要去判斷是否需要去設置緩存,采用何種方式去實現緩存?nopCommerce項目中給我提供了很好的例子,在實際應用可以借鑒其實現方式,增強我們系統的用戶體驗。
相關資料: