你的位置:首页 > ASP.net教程

[ASP.net教程]Nop中的Cache浅析


Nop中定义了ICacheManger接口,它有几个实现,其中MemoryCacheManager是内存缓存的一个实现。

MemoryCacheManager:

using System;using System.Collections.Generic;using System.Runtime.Caching;using System.Text.RegularExpressions;namespace Nop.Core.Caching{  /// <summary>  /// Represents a manager for caching between HTTP requests (long term caching)  /// </summary>  public partial class MemoryCacheManager : ICacheManager  {    /// <summary>    /// Cache object    /// </summary>    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 virtual 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 virtual 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 virtual 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 virtual void Remove(string key)    {      Cache.Remove(key);    }    /// <summary>    /// Removes items by pattern    /// </summary>    /// <param name="pattern">pattern</param>    public virtual 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 virtual void Clear()    {      foreach (var item in Cache)        Remove(item.Key);    }    /// <summary>    /// Dispose    /// </summary>    public virtual void Dispose()    {    }  }}

缓存的添加,在需要的地方构建cache key然后调用ICacheManger接口存储起来:

 var cachedModel = _cacheManager.Get(cacheKey, () =>      {        var model = new List<BlogPostYearModel>();        var blogPosts = _blogService.GetAllBlogPosts(_storeContext.CurrentStore.Id,           _workContext.WorkingLanguage.Id);        if (blogPosts.Count > 0)        {          var months = new SortedDictionary<DateTime, int>();          var first = blogPosts[blogPosts.Count - 1].CreatedOnUtc;          while (DateTime.SpecifyKind(first, DateTimeKind.Utc) <= DateTime.UtcNow.AddMonths(1))          {            var list = blogPosts.GetPostsByDate(new DateTime(first.Year, first.Month, 1), new DateTime(first.Year, first.Month, 1).AddMonths(1).AddSeconds(-1));            if (list.Count > 0)            {              var date = new DateTime(first.Year, first.Month, 1);              months.Add(date, list.Count);            }            first = first.AddMonths(1);          }          int current = 0;          foreach (var kvp in months)          {            var date = kvp.Key;            var blogPostCount = kvp.Value;            if (current == 0)              current = date.Year;            if (date.Year > current || model.Count == 0)            {              var yearModel = new BlogPostYearModel              {                Year = date.Year              };              model.Add(yearModel);            }            model.Last().Months.Add(new BlogPostMonthModel            {              Month = date.Month,              BlogPostCount = blogPostCount            });            current = date.Year;          }        }        return model;      });

这个ICacheManger的Get方法其实是个扩展方法,当获取不到缓存的时候调用Func<T>获取值,然后缓存起来:

using System;namespace Nop.Core.Caching{  /// <summary>  /// Extensions  /// </summary>  public static class CacheExtensions  {    /// <summary>    /// Get a cached item. If it's not in the cache yet, then load and cache it    /// </summary>    /// <typeparam name="T">Type</typeparam>    /// <param name="cacheManager">Cache manager</param>    /// <param name="key">Cache key</param>    /// <param name="acquire">Function to load item if it's not in the cache yet</param>    /// <returns>Cached item</returns>    public static T Get<T>(this ICacheManager cacheManager, string key, Func<T> acquire)    {      return Get(cacheManager, key, 60, acquire);    }    /// <summary>    /// Get a cached item. If it's not in the cache yet, then load and cache it    /// </summary>    /// <typeparam name="T">Type</typeparam>    /// <param name="cacheManager">Cache manager</param>    /// <param name="key">Cache key</param>    /// <param name="cacheTime">Cache time in minutes (0 - do not cache)</param>    /// <param name="acquire">Function to load item if it's not in the cache yet</param>    /// <returns>Cached item</returns>    public static T Get<T>(this ICacheManager cacheManager, string key, int cacheTime, Func<T> acquire)    {      if (cacheManager.IsSet(key))      {        return cacheManager.Get<T>(key);      }      var result = acquire();      if (cacheTime > 0)        cacheManager.Set(key, result, cacheTime);      return result;    }  }}

Cache的移除。Nop缓存的移除比较有意思,它使用Pub/Sub模式来实现。

当你缓存一个Blog的列表,如果后面对某个Blog进行Update的时候,你就有两个选择:1.更新这个Blog的cache 2.移除所有关于Blog的cache。Nop选择的是后者,因为第一种方案实现起来的代价有点大,你可能需要给单独每个Blog指定一个Key来缓存起来,或者遍历所有关于Blog的cache。

当发生Blog的Update的时候,会发送一个通知事件:

 public virtual void UpdateBlogPost(BlogPost blogPost)    {      if (blogPost == null)        throw new ArgumentNullException("blogPost");      _blogPostRepository.Update(blogPost);      //event notification      _eventPublisher.EntityUpdated(blogPost);    }    

看一下EventPublish的实现 :

 public interface IEventPublisher  {    /// <summary>    /// Publish event    /// </summary>    /// <typeparam name="T">Type</typeparam>    /// <param name="eventMessage">Event message</param>    void Publish<T>(T eventMessage);  }using System;using System.Linq;using Nop.Core.Infrastructure;using Nop.Core.Plugins;using Nop.Services.Logging;namespace Nop.Services.Events{  /// <summary>  /// Evnt publisher  /// </summary>  public class EventPublisher : IEventPublisher  {    private readonly ISubscriptionService _subscriptionService;    /// <summary>    /// Ctor    /// </summary>    /// <param name="subscriptionService"></param>    public EventPublisher(ISubscriptionService subscriptionService)    {      _subscriptionService = subscriptionService;    }    /// <summary>    /// Publish to cunsumer    /// </summary>    /// <typeparam name="T">Type</typeparam>    /// <param name="x">Event consumer</param>    /// <param name="eventMessage">Event message</param>    protected virtual void PublishToConsumer<T>(IConsumer<T> x, T eventMessage)    {      //Ignore not installed plugins      var plugin = FindPlugin(x.GetType());      if (plugin != null && !plugin.Installed)        return;      try      {        x.HandleEvent(eventMessage);      }      catch (Exception exc)      {        //log error        var logger = EngineContext.Current.Resolve<ILogger>();        //we put in to nested try-catch to prevent possible cyclic (if some error occurs)        try        {          logger.Error(exc.Message, exc);        }        catch (Exception)        {          //do nothing        }      }    }    /// <summary>    /// Find a plugin descriptor by some type which is located into its assembly    /// </summary>    /// <param name="providerType">Provider type</param>    /// <returns>Plugin descriptor</returns>    protected virtual PluginDescriptor FindPlugin(Type providerType)    {      if (providerType == null)        throw new ArgumentNullException("providerType");      if (PluginManager.ReferencedPlugins == null)        return null;      foreach (var plugin in PluginManager.ReferencedPlugins)      {        if (plugin.ReferencedAssembly == null)          continue;        if (plugin.ReferencedAssembly.FullName == providerType.Assembly.FullName)          return plugin;      }      return null;    }    /// <summary>    /// Publish event    /// </summary>    /// <typeparam name="T">Type</typeparam>    /// <param name="eventMessage">Event message</param>    public virtual void Publish<T>(T eventMessage)    {      var subscriptions = _subscriptionService.GetSubscriptions<T>();      subscriptions.ToList().ForEach(x => PublishToConsumer(x, eventMessage));    }  }}

很简单,只是获取所有的订阅,然后依次调用其中的PublishToConsumer方法。

那么订阅是在哪里呢?

首先这是Blog消息消费者的定义:

using Nop.Core.Caching;using Nop.Core.Domain.Blogs;using Nop.Core.Domain.Catalog;using Nop.Core.Domain.Configuration;using Nop.Core.Domain.Directory;using Nop.Core.Domain.Localization;using Nop.Core.Domain.Media;using Nop.Core.Domain.News;using Nop.Core.Domain.Orders;using Nop.Core.Domain.Polls;using Nop.Core.Domain.Topics;using Nop.Core.Domain.Vendors;using Nop.Core.Events;using Nop.Core.Infrastructure;using Nop.Services.Events;namespace Nop.Web.Infrastructure.Cache{  /// <summary>  /// Model cache event consumer (used for caching of presentation layer models)  /// </summary>  public partial class ModelCacheEventConsumer:        //blog posts    IConsumer<EntityInserted<BlogPost>>,    IConsumer<EntityUpdated<BlogPost>>,    IConsumer<EntityDeleted<BlogPost>>     {    /// <summary>    /// Key for blog tag list model    /// </summary>    /// <remarks>    /// {0} : language ID    /// {1} : current store ID    /// </remarks>    public const string BLOG_TAGS_MODEL_KEY = "Nop.pres.blog.tags-{0}-{1}";    /// <summary>    /// Key for blog archive (years, months) block model    /// </summary>    /// <remarks>    /// {0} : language ID    /// {1} : current store ID    /// </remarks>    public const string BLOG_MONTHS_MODEL_KEY = "Nop.pres.blog.months-{0}-{1}";    public const string BLOG_PATTERN_KEY = "Nop.pres.blog";    private readonly ICacheManager _cacheManager;        public ModelCacheEventConsumer()    {      //TODO inject static cache manager using constructor      this._cacheManager = EngineContext.Current.ContainerManager.Resolve<ICacheManager>("nop_cache_static");    }    //Blog posts    public void HandleEvent(EntityInserted<BlogPost> eventMessage)    {      _cacheManager.RemoveByPattern(BLOG_PATTERN_KEY);    }    public void HandleEvent(EntityUpdated<BlogPost> eventMessage)    {      _cacheManager.RemoveByPattern(BLOG_PATTERN_KEY);    }    public void HandleEvent(EntityDeleted<BlogPost> eventMessage)    {      _cacheManager.RemoveByPattern(BLOG_PATTERN_KEY);    }  }}

所有的Blog的key都采用统一的前缀,Nop.pres.blog。这样只要使用这个前缀就能清楚所有关于blog的缓存了。

这个类继承了3个接口所以有3个HandleEvent的实现,都是清楚blog相关的缓存。

这些消费者其实并未主动的去注册订阅,而是通过反射在启动的时候自动加载进IoC容器里的,当需要使用的时候通过接口直接取出来使用。

      //Register event consumers      var consumers = typeFinder.FindClassesOfType(typeof(IConsumer<>)).ToList();      foreach (var consumer in consumers)      {        builder.RegisterType(consumer)          .As(consumer.FindInterfaces((type, criteria) =>          {            var isMatch = type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition());            return isMatch;          }, typeof(IConsumer<>)))          .InstancePerLifetimeScope();      }      builder.RegisterType<EventPublisher>().As<IEventPublisher>().SingleInstance();      builder.RegisterType<SubscriptionService>().As<ISubscriptionService>().SingleInstance();

其中Pub/Sub是其中的精髓,非常值得学习。