• 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是其中的精髓,非常值得学习。

  • 相关阅读:
    【NXOpen.UF扩展】修改表达式
    NX二次开发 克隆
    C++手动加载CLR运行托管程序(CLR Hosting)
    C++/CLR 使用(VS2012,VS2013,VS2015)编写
    解决VS2015安装后stdio.h ucrtd.lib等文件无法识别问题,即include+lib环境变量配置
    NX 图标
    【错误分析】NX error status: 32
    NX CAM 读取加工参数
    CAM 模板样式表
    锁定NX原生界面 & 锁定界面更新
  • 原文地址:https://www.cnblogs.com/kklldog/p/5621916.html
Copyright © 2020-2023  润新知