nopCommerce架构分析系列(二)数据Cache

时间:2023-03-09 09:42:44
nopCommerce架构分析系列(二)数据Cache

原文(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 部分缓存框架相关的类

nopCommerce架构分析系列(二)数据Cache

2、基于HttpContextBase页面请求级数据缓存

HttpContextBase 类为抽象类,该类包含的成员与 HttpContext 类相同。 使用 HttpContextBase 类可以创建一些派生类,这些派生类与

HttpContext 类相似,但是可以进行自定义并在 ASP.NET 管道外部使用。 在执行单元测试时,通常使用派生类实现具有自定义行为的成员以实现正在测试的方案,这更容易进行单元测试。HttpContextWrapper 类是从 HttpContextBase 类派生的。 HttpContextWrapper 类用作 HttpContext 类的包装。 在运行时,通常使用 HttpContextWrapper 类的实例调用 HttpContext 对象上的成员。

HttpContext的Items集合是IDictionary键/值对的对象集合,在HttpRequest的生存期*享。存储成本很高的调用的结果,防止该调用在页面上出现多次。一个HttpRequest中的各个单元需要处理相同或类似的数据。如果数据的生存期只是一个请求,就可以考虑使用HttpContext. Items作为短期的高速缓存。

nopCommerce项目中的缓存

1、缓存的实现

nopCommerce项目缓存类层级图

nopCommerce架构分析系列(二)数据Cache

ICacheManager接口,该接口定义了数据缓存常用的方法。

 public interface ICacheManager
{
/// <summary>
/// Gets or sets the value associated with the specified key.
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <param name="key">The key of the value to get.</param>
/// <returns>The value associated with the specified key.</returns>
T Get<T>(string key); /// <summary>
/// Adds the specified key and object to the cache.
/// </summary>
/// <param name="key">key</param>
/// <param name="data">Data</param>
/// <param name="cacheTime">Cache time</param>
void Set(string key, object data, int cacheTime); /// <summary>
/// Gets a value indicating whether the value associated with the specified key is cached
/// </summary>
/// <param name="key">key</param>
/// <returns>Result</returns>
bool IsSet(string key); /// <summary>
/// Removes the value with the specified key from the cache
/// </summary>
/// <param name="key">/key</param>
void Remove(string key); /// <summary>
/// Removes items by pattern
/// </summary>
/// <param name="pattern">pattern</param>
void RemoveByPattern(string pattern); /// <summary>
/// Clear all cache data
/// </summary>
void Clear();
}

CacheExtensions扩展方法对ICacheManager进行扩展。

 /// <summary>
/// Extensions
/// </summary>
public static class CacheExtensions
{
public static T Get<T>(this ICacheManager cacheManager, string key, Func<T> acquire)
{
return Get(cacheManager, key, , acquire);
} public static T Get<T>(this ICacheManager cacheManager, string key, int cacheTime, Func<T> acquire)
{
if (cacheManager.IsSet(key))
{
return cacheManager.Get<T>(key);
}
else
{
var result = acquire();
//if (result != null)
cacheManager.Set(key, result, cacheTime);
return result;
}
}
}

MemoryCacheCache类,使用.net  缓存框架实现数据缓存

 /// <summary>
/// Represents a MemoryCacheCache
/// </summary>
public partial class MemoryCacheManager : ICacheManager
{
protected ObjectCache Cache
{
get
{
return MemoryCache.Default;
}
} /// <summary>
/// Gets or sets the value associated with the specified key.
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <param name="key">The key of the value to get.</param>
/// <returns>The value associated with the specified key.</returns>
public T Get<T>(string key)
{
return (T)Cache[key];
} /// <summary>
/// Adds the specified key and object to the cache.
/// </summary>
/// <param name="key">key</param>
/// <param name="data">Data</param>
/// <param name="cacheTime">Cache time</param>
public void Set(string key, object data, int cacheTime)
{
if (data == null)
return; var policy = new CacheItemPolicy();
policy.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime);
Cache.Add(new CacheItem(key, data), policy);
} /// <summary>
/// Gets a value indicating whether the value associated with the specified key is cached
/// </summary>
/// <param name="key">key</param>
/// <returns>Result</returns>
public bool IsSet(string key)
{
return (Cache.Contains(key));
} /// <summary>
/// Removes the value with the specified key from the cache
/// </summary>
/// <param name="key">/key</param>
public void Remove(string key)
{
Cache.Remove(key);
} /// <summary>
/// Removes items by pattern
/// </summary>
/// <param name="pattern">pattern</param>
public void RemoveByPattern(string pattern)
{
var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
var keysToRemove = new List<String>(); foreach (var item in Cache)
if (regex.IsMatch(item.Key))
keysToRemove.Add(item.Key); foreach (string key in keysToRemove)
{
Remove(key);
}
} /// <summary>
/// Clear all cache data
/// </summary>
public void Clear()
{
foreach (var item in Cache)
Remove(item.Key);
}
}

PerRequestCacheManager类,实现页面请求级的数据缓存。

 /// <summary>
/// Represents a NopStaticCache
/// </summary>
public partial class PerRequestCacheManager : ICacheManager
{
private readonly HttpContextBase _context; /// <summary>
/// Ctor
/// </summary>
/// <param name="context">Context</param>
public PerRequestCacheManager(HttpContextBase context)
{
this._context = context;
} /// <summary>
/// Creates a new instance of the NopRequestCache class
/// </summary>
protected IDictionary GetItems()
{
if (_context != null)
return _context.Items; return null;
} /// <summary>
/// Gets or sets the value associated with the specified key.
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <param name="key">The key of the value to get.</param>
/// <returns>The value associated with the specified key.</returns>
public T Get<T>(string key)
{
var items = GetItems();
if (items == null)
return default(T); return (T)items[key];
} /// <summary>
/// Adds the specified key and object to the cache.
/// </summary>
/// <param name="key">key</param>
/// <param name="data">Data</param>
/// <param name="cacheTime">Cache time</param>
public void Set(string key, object data, int cacheTime)
{
var items = GetItems();
if (items == null)
return; if (data != null)
{
if (items.Contains(key))
items[key] = data;
else
items.Add(key, data);
}
} /// <summary>
/// Gets a value indicating whether the value associated with the specified key is cached
/// </summary>
/// <param name="key">key</param>
/// <returns>Result</returns>
public bool IsSet(string key)
{
var items = GetItems();
if (items == null)
return false; return (items[key] != null);
} /// <summary>
/// Removes the value with the specified key from the cache
/// </summary>
/// <param name="key">/key</param>
public void Remove(string key)
{
var items = GetItems();
if (items == null)
return; items.Remove(key);
} /// <summary>
/// Removes items by pattern
/// </summary>
/// <param name="pattern">pattern</param>
public void RemoveByPattern(string pattern)
{
var items = GetItems();
if (items == null)
return; var enumerator = items.GetEnumerator();
var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
var keysToRemove = new List<String>();
while (enumerator.MoveNext())
{
if (regex.IsMatch(enumerator.Key.ToString()))
{
keysToRemove.Add(enumerator.Key.ToString());
}
} foreach (string key in keysToRemove)
{
items.Remove(key);
}
} /// <summary>
/// Clear all cache data
/// </summary>
public void Clear()
{
var items = GetItems();
if (items == null)
return; var enumerator = items.GetEnumerator();
var keysToRemove = new List<String>();
while (enumerator.MoveNext())
{
keysToRemove.Add(enumerator.Key.ToString());
} foreach (string key in keysToRemove)
{
items.Remove(key);
}
}
}

NopNullCache类,空的数据缓存类。

 /// <summary>
/// Represents a NopNullCache
/// </summary>
public partial class NopNullCache : ICacheManager
{
/// <summary>
/// Gets or sets the value associated with the specified key.
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <param name="key">The key of the value to get.</param>
/// <returns>The value associated with the specified key.</returns>
public T Get<T>(string key)
{
return default(T);
} /// <summary>
/// Adds the specified key and object to the cache.
/// </summary>
/// <param name="key">key</param>
/// <param name="data">Data</param>
/// <param name="cacheTime">Cache time</param>
public void Set(string key, object data, int cacheTime)
{
} /// <summary>
/// Gets a value indicating whether the value associated with the specified key is cached
/// </summary>
/// <param name="key">key</param>
/// <returns>Result</returns>
public bool IsSet(string key)
{
return false;
} /// <summary>
/// Removes the value with the specified key from the cache
/// </summary>
/// <param name="key">/key</param>
public void Remove(string key)
{
} /// <summary>
/// Removes items by pattern
/// </summary>
/// <param name="pattern">pattern</param>
public void RemoveByPattern(string pattern)
{
} /// <summary>
/// Clear all cache data
/// </summary>
public void Clear()
{
}
}

2、缓存的应用

下面是BlogService类中的CRUD,从中我们可以了解到,数据缓存是如何处理的,在数据检索时,直接从缓存取数据,其他方法均根据相关正则表达式移除BlogPost的所有缓存,以避免读取到脏数据。

 /// <summary>
/// Gets a blog post
/// </summary>
/// <param name="blogPostId">Blog post identifier</param>
/// <returns>Blog post</returns>
public virtual BlogPost GetBlogPostById(int blogPostId)
{
if (blogPostId == )
return null; string key = string.Format(BLOGPOST_BY_ID_KEY, blogPostId);
return _cacheManager.Get(key, () =>
{
var pv = _blogPostRepository.GetById(blogPostId);
return pv;
});
} /// <summary>
/// Deletes a blog post
/// </summary>
/// <param name="blogPost">Blog post</param>
public virtual void DeleteBlogPost(BlogPost blogPost)
{
if (blogPost == null)
throw new ArgumentNullException("blogPost"); _blogPostRepository.Delete(blogPost); _cacheManager.RemoveByPattern(BLOGPOST_PATTERN_KEY); //event notification
_eventPublisher.EntityDeleted(blogPost);
} /// <summary>
/// Inserts an blog post
/// </summary>
/// <param name="blogPost">Blog post</param>
public virtual void InsertBlogPost(BlogPost blogPost)
{
if (blogPost == null)
throw new ArgumentNullException("blogPost"); _blogPostRepository.Insert(blogPost); _cacheManager.RemoveByPattern(BLOGPOST_PATTERN_KEY); //event notification
_eventPublisher.EntityInserted(blogPost);
} /// <summary>
/// Updates the blog post
/// </summary>
/// <param name="blogPost">Blog post</param>
public virtual void UpdateBlogPost(BlogPost blogPost)
{
if (blogPost == null)
throw new ArgumentNullException("blogPost"); _blogPostRepository.Update(blogPost); _cacheManager.RemoveByPattern(BLOGPOST_PATTERN_KEY); //event notification
_eventPublisher.EntityUpdated(blogPost);
}

下面是nopCommerce中该部分的依赖注入部分:ps:nopCommerce的依赖注入会在以后为大家介绍:)

 //HTTP context and other related stuff
builder.Register(c =>
//register FakeHttpContext when HttpContext is not available
HttpContext.Current != null ?
(new HttpContextWrapper(HttpContext.Current) as HttpContextBase) :
(new FakeHttpContext("~/") as HttpContextBase))
.As<HttpContextBase>()
.InstancePerHttpRequest();
builder.Register(c => c.Resolve<HttpContextBase>().Request)
.As<HttpRequestBase>()
.InstancePerHttpRequest();
builder.Register(c => c.Resolve<HttpContextBase>().Response)
.As<HttpResponseBase>()
.InstancePerHttpRequest();
builder.Register(c => c.Resolve<HttpContextBase>().Server)
.As<HttpServerUtilityBase>()
.InstancePerHttpRequest();
builder.Register(c => c.Resolve<HttpContextBase>().Session)
.As<HttpSessionStateBase>()
.InstancePerHttpRequest();
//cache manager
builder.RegisterType<MemoryCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_static").SingleInstance();
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