序言
在很多访问量较大的系统中,尤其在某一项数据访问频次较高时,我们会考虑使用缓存,减少系统和数据库的交互,以达到良好的用户体验。缓存主要有页面缓存和数据缓存。数据缓存的实现有很多方式,有基于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接口,该接口定义了数据缓存常用的方法。
View Code
1
public
interface 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进行扩展。
View Code
1
///
<summary>
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 缓存框架实现数据缓存
View Code
1
///
<summary>
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类,实现页面请求级的数据缓存。
View Code
1
///
<summary>
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类,空的数据缓存类。
View Code
1
///
<summary>
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的所有缓存,以避免读取到脏数据。
View Code
1
///
<summary>
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的依赖注入会在以后为大家介绍:)
View Code
1
//
HTTP context and other related stuff
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项目中给我提供了很好的例子,在实际应用可以借鉴其实现方式,增强我们系统的用户体验。
相关资料:
1、为短时间状态存储应用HttpContext.Current.Items
2、全面认识一下.NET 4.0的缓存功能
3、HttpContext是干什么的
4、为什么是HttpContextBase而不是IHttpContext