原文(http://www.cnblogs.com/gusixing/archive/2012/04/12/2443799.html)非常感谢作者顾思行的分享!
序言
在很多访问量较大的系统中,尤其在某一项数据访问频次较高时,我们会考虑使用缓存,减少系统和数据库的交互,以达到良好的用户体验。缓存主要有页面缓存和数据缓存。数据缓存的实现有很多方式,有基于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接口,该接口定义了数据缓存常用的方法。
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进行扩展。
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 缓存框架实现数据缓存
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类,实现页面请求级的数据缓存。
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类,空的数据缓存类。
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的所有缓存,以避免读取到脏数据。
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的依赖注入会在以后为大家介绍:)
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
4、为什么是HttpContextBase而不是IHttpContext