Lind.DDD.Caching分布式数据集缓存介绍

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介:

戏说当年

大叔原创的分布式数据集缓存在之前的企业级框架里介绍过,大家可以关注《我心中的核心组件(可插拔的AOP)~第二回 缓存拦截器》,而今天主要对Lind.DDD.Caching进行更全面的解决,设计思想和主要核心内容进行讲解。其实在很多缓存架构在业界有很多,向.net运行时里也有Cache,也可以实现简单的数据缓存的功能,向前几年页面的静态化比较流行,就出现了很多Http的“拦截器“,对当前HTTP响应的内容进行完整的页面缓存,缓存的文件大多数存储到磁盘里,访问的时间直接将磁盘上的HTML文件进行输出,不用asp.net进行解析,也省去了链数据库的操作,所以在性能上有所提升,弊端就是和当前的页面(HTML内容)耦合度太大,所以,现在用这种原始的缓存方式的项目越来越少。

大叔的数据集缓存

比较页面缓存,数据集缓存就感觉优异了不少,它缓存的是数据,而不是页面,即它省去了链接数据库的时间,而直接用缓存,文件,redis等中间件上返回内容,当前你的中间件为了提升性能,可以采用集群机制,这在一些NoSql上实现非常容易,或者说Nosql就是为了缓存而产生的,呵呵!

缓存特性

这个CachingAttribute 特性被使用者添加到指定的方法上,有get,put,remove等枚举类型,分别为读缓存,写缓存和删除缓存。

  /// <summary>
    /// 表示由此特性所描述的方法,能够获得来自Microsoft.Practices.EnterpriseLibrary.Caching基础结构层所提供的缓存功能。
    /// </summary>
    [AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=false)]
    public class CachingAttribute : Attribute
    {
        #region Ctor
        /// <summary>
        /// 初始化一个新的<c>CachingAttribute</c>类型。
        /// </summary>
        /// <param name="method">缓存方式。</param>
        public CachingAttribute(CachingMethod method)
        {
            this.Method = method;
        }
        /// <summary>
        /// 初始化一个新的<c>CachingAttribute</c>类型。
        /// </summary>
        /// <param name="method">缓存方式。</param>
        /// <param name="correspondingMethodNames">与当前缓存方式相关的方法名称。注:此参数仅在缓存方式为Remove时起作用。</param>
        public CachingAttribute(CachingMethod method, params string[] correspondingMethodNames)
            : this(method)
        {
            this.CorrespondingMethodNames = correspondingMethodNames;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// 获取或设置缓存方式。
        /// </summary>
        public CachingMethod Method { get; set; }
        /// <summary>
        /// 获取或设置一个<see cref="Boolean"/>值,该值表示当缓存方式为Put时,是否强制将值写入缓存中。
        /// </summary>
        public bool Force { get; set; }
        /// <summary>
        /// 获取或设置与当前缓存方式相关的方法名称。注:此参数仅在缓存方式为Remove时起作用。
        /// </summary>
        public string[] CorrespondingMethodNames { get; set; }
        #endregion
    }

缓存拦截器

拦截器起源于面向切面的编程aop里,它也是aop设计的精髓,即将指定方法拦截,然后注入新的代码逻辑,在不修改原有代码的情况下,完成这个功能,在拦截器里,我们为不同的项目添加了不同的名称,这是为了避免在多项目情况下,缓存键名重复的问题,因为我们的缓存内容都是存储在同一个中间件上的。

    /// <summary>
    /// 表示用于方法缓存功能的拦截行为。
    /// </summary>
    public class CachingBehavior : IInterceptionBehavior
    {
        /// <summary>
        /// 缓存项目名称,每个项目有自己的名称
        /// 避免缓存键名重复
        /// </summary>
        static readonly string cacheProjectName = System.Configuration.ConfigurationManager.AppSettings["CacheProjectName"] ?? "DataSetCache";


        #region Private Methods
        /// <summary>
        /// 根据指定的<see cref="CachingAttribute"/>以及<see cref="IMethodInvocation"/>实例,
        /// 获取与某一特定参数值相关的键名。
        /// </summary>
        /// <param name="cachingAttribute"><see cref="CachingAttribute"/>实例。</param>
        /// <param name="input"><see cref="IMethodInvocation"/>实例。</param>
        /// <returns>与某一特定参数值相关的键名。</returns>
        private string GetValueKey(CachingAttribute cachingAttribute, IMethodInvocation input)
        {
            switch (cachingAttribute.Method)
            {
                // 如果是Remove,则不存在特定值键名,所有的以该方法名称相关的缓存都需要清除
                case CachingMethod.Remove:
                    return null;
                case CachingMethod.Get:// 如果是Get或者Put,则需要产生一个针对特定参数值的键名
                case CachingMethod.Put:
                    if (input.Arguments != null &&
                        input.Arguments.Count > 0)
                    {
                        var sb = new StringBuilder();
                        for (int i = 0; i < input.Arguments.Count; i++)
                        {
                            if (input.Arguments[i] == null)
                                break;

                            if (input.Arguments[i].GetType().BaseType == typeof(LambdaExpression))//lambda处理
                            {
                                string result = "";

                                try
                                {
                                    var exp = input.Arguments[i] as LambdaExpression;
                                    var arr = ((System.Runtime.CompilerServices.Closure)(((System.Delegate)(Expression.Lambda(exp).Compile().DynamicInvoke())).Target)).Constants;
                                    Type t = arr[0].GetType();
                                    foreach (var member in t.GetFields())
                                    {
                                        result += "_" + member.Name + "_" + t.GetField(member.Name).GetValue(arr[0]);
                                    }
                                    result = result.Remove(0, 1);
                                }
                                catch (NullReferenceException)
                                {
                                    //lambda表达式异常,可能是没有字段,如这种格式i=>true,会产生NullReferenceException异常.
                                }

                                sb.Append(result.ToString());
                            }
                            else if (input.Arguments[i].GetType() != typeof(string)//类和结构体处理
                                && input.Arguments[i].GetType().BaseType.IsClass)
                            {
                                var obj = input.Arguments[i];
                                Type t = obj.GetType();
                                string result = "";
                                #region 提取类中的字段和属性
                                foreach (var member in t.GetProperties())//公开属性
                                {
                                    result += member.Name + "_" + t.GetProperty(member.Name).GetValue(obj) + "_";
                                }
                                foreach (var member in t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))//私有和公用字段
                                {
                                    result += member.Name + "_" + t.GetField(member.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetValue(obj) + "_";
                                }
                                #endregion
                                result = result.Remove(result.Length - 1);
                                sb.Append(result.ToString());
                            }
                            else//简单值类型处理
                            {
                                sb.Append(input.Arguments[i].ToString());
                            }

                            if (i != input.Arguments.Count - 1)
                                sb.Append("_");
                        }
                        return sb.ToString();
                    }
                    else
                        return "NULL";
                default:
                    throw new InvalidOperationException("无效的缓存方式。");
            }
        }
        #endregion

        #region IInterceptionBehavior Members
        /// <summary>
        /// 获取当前行为需要拦截的对象类型接口。
        /// </summary>
        /// <returns>所有需要拦截的对象类型接口。</returns>
        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }

        /// <summary>
        /// 通过实现此方法来拦截调用并执行所需的拦截行为。
        /// </summary>
        /// <param name="input">调用拦截目标时的输入信息。</param>
        /// <param name="getNext">通过行为链来获取下一个拦截行为的委托。</param>
        /// <returns>从拦截目标获得的返回信息。</returns>
        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {

            var method = input.MethodBase;
            //键值前缀
            string prefix = cacheProjectName + "_";
            var baseInterfaces = input.Target.GetType().GetInterfaces();
            if (baseInterfaces != null && baseInterfaces.Any())
            {
                foreach (var item in baseInterfaces)
                {
                    prefix += item.ToString() + "_";
                }
            }


            //键名,在put和get时使用
            var key = prefix + method.Name;

            if (method.IsDefined(typeof(CachingAttribute), false))
            {
                var cachingAttribute = (CachingAttribute)method.GetCustomAttributes(typeof(CachingAttribute), false)[0];
                var valKey = GetValueKey(cachingAttribute, input);
                switch (cachingAttribute.Method)
                {
                    case CachingMethod.Get:
                        try
                        {
                            if (CacheManager.Instance.Exists(key, valKey))
                            {
                                var obj = CacheManager.Instance.Get(key, valKey);
                                var arguments = new object[input.Arguments.Count];
                                input.Arguments.CopyTo(arguments, 0);
                                return new VirtualMethodReturn(input, obj, arguments);
                            }
                            else
                            {
                                var methodReturn = getNext().Invoke(input, getNext);
                                CacheManager.Instance.Add(key, valKey, methodReturn.ReturnValue);
                                return methodReturn;
                            }
                        }
                        catch (Exception ex)
                        {
                            return new VirtualMethodReturn(input, ex);
                        }
                    case CachingMethod.Put:
                        try
                        {
                            var methodReturn = getNext().Invoke(input, getNext);
                            if (CacheManager.Instance.Exists(key))
                            {
                                if (cachingAttribute.Force)
                                {
                                    CacheManager.Instance.Remove(key);
                                    CacheManager.Instance.Add(key, valKey, methodReturn.ReturnValue);
                                }
                                else
                                    CacheManager.Instance.Put(key, valKey, methodReturn.ReturnValue);
                            }
                            else
                                CacheManager.Instance.Add(key, valKey, methodReturn.ReturnValue);
                            return methodReturn;
                        }
                        catch (Exception ex)
                        {
                            return new VirtualMethodReturn(input, ex);
                        }
                    case CachingMethod.Remove:
                        try
                        {
                            var removeKeys = cachingAttribute.CorrespondingMethodNames;
                            foreach (var removeKey in removeKeys)
                            {
                                string delKey = prefix + removeKey;
                                if (CacheManager.Instance.Exists(delKey))
                                    CacheManager.Instance.Remove(delKey);
                            }
                            var methodReturn = getNext().Invoke(input, getNext);
                            return methodReturn;
                        }
                        catch (Exception ex)
                        {
                            return new VirtualMethodReturn(input, ex);
                        }
                    default: break;
                }
            }

            return getNext().Invoke(input, getNext);
        }

        /// <summary>
        /// 获取一个<see cref="Boolean"/>值,该值表示当前拦截行为被调用时,是否真的需要执行
        /// 某些操作。
        /// </summary>
        public bool WillExecute
        {
            get { return true; }
        }

        #endregion
    }

缓存实现者

目前大叔的框架中,数据集缓存有redis和内容两种实现方式,在多web服务器的情况下,只能采用redis这种中间存储服务器。

缓存生产者

缓存生产者与日志,消息等生产者类似,由于全局使用一个实例即可,所以在设计时采用了单例模式,工厂模式,策略模式等,目前在工厂里只有EntLib内存缓存和redis分布式缓存两种,详细请见代码。

    /// <summary>
    /// 缓存持久化工厂类
    /// 可以由多种持久化的策略
    /// 策略模式和工厂模式的体现
    /// </summary>
    public sealed class CacheManager : ICacheProvider
    {
        #region Private Fields
        private readonly ICacheProvider _cacheProvider;
        private static readonly CacheManager _instance;
        #endregion

        #region Ctor
        static CacheManager() { _instance = new CacheManager(); }

        /// <summary>
        /// 对外不能创建类的实例
        /// </summary>
        private CacheManager()
        {
            string strategyName = ConfigConstants.ConfigManager.Config.AoP_CacheStrategy ?? "EntLib";

            switch (strategyName)
            {
                case "EntLib":
                    _cacheProvider = new EntLibCacheProvider();
                    break;
                case "Redis":
                    _cacheProvider = new RedisCacheProvider();
                    break;
                default:
                    throw new ArgumentException("缓存持久化方法不正确,目前只支持EntLib和Redis");
            }
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// 获取<c>CacheManager</c>类型的单件(Singleton)实例。
        /// </summary>
        public static CacheManager Instance
        {
            get { return _instance; }
        }
        #endregion

        #region ICacheProvider Members
        /// <summary>
        /// 向缓存中添加一个对象。
        /// </summary>
        /// <param name="key">缓存的键值,该值通常是使用缓存机制的方法的名称。</param>
        /// <param name="valKey">缓存值的键值,该值通常是由使用缓存机制的方法的参数值所产生。</param>
        /// <param name="value">需要缓存的对象。</param>
        public void Add(string key, string valKey, object value)
        {
            _cacheProvider.Add(key, valKey, value);
        }
        /// <summary>
        /// 向缓存中更新一个对象。
        /// </summary>
        /// <param name="key">缓存的键值,该值通常是使用缓存机制的方法的名称。</param>
        /// <param name="valKey">缓存值的键值,该值通常是由使用缓存机制的方法的参数值所产生。</param>
        /// <param name="value">需要缓存的对象。</param>
        public void Put(string key, string valKey, object value)
        {
            _cacheProvider.Put(key, valKey, value);
        }
        /// <summary>
        /// 从缓存中读取对象。
        /// </summary>
        /// <param name="key">缓存的键值,该值通常是使用缓存机制的方法的名称。</param>
        /// <param name="valKey">缓存值的键值,该值通常是由使用缓存机制的方法的参数值所产生。</param>
        /// <returns>被缓存的对象。</returns>
        public object Get(string key, string valKey)
        {
            return _cacheProvider.Get(key, valKey);
        }
        /// <summary>
        /// 从缓存中移除对象。
        /// </summary>
        /// <param name="key">缓存的键值,该值通常是使用缓存机制的方法的名称。</param>
        public void Remove(string key)
        {
            _cacheProvider.Remove(key);
        }
        /// <summary>
        /// 获取一个<see cref="Boolean"/>值,该值表示拥有指定键值的缓存是否存在。
        /// </summary>
        /// <param name="key">指定的键值。</param>
        /// <returns>如果缓存存在,则返回true,否则返回false。</returns>
        public bool Exists(string key)
        {
            return _cacheProvider.Exists(key);
        }
        /// <summary>
        /// 获取一个<see cref="Boolean"/>值,该值表示拥有指定键值和缓存值键的缓存是否存在。
        /// </summary>
        /// <param name="key">指定的键值。</param>
        /// <param name="valKey">缓存值键。</param>
        /// <returns>如果缓存存在,则返回true,否则返回false。</returns>
        public bool Exists(string key, string valKey)
        {
            return _cacheProvider.Exists(key, valKey);
        }
        #endregion
    }

最后,我们的缓存使用需要在接口的方法或者虚方法上进行声明,因为我们的拦截使用了Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension拦截器组件,实现原因是生成一个代理类,并重写指定的被拦截的方法,所以要求你的方法是接口方法或者虚方法。

感谢各位的耐心阅读,请继续关注Lind.DDD大叔框架设计437541737

本文转自博客园张占岭(仓储大叔)的博客,原文链接:Lind.DDD.Caching分布式数据集缓存介绍,如需转载请自行联系原博主。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
5月前
|
缓存 NoSQL Java
聊聊分布式应用中的缓存方案(一)
聊聊分布式应用中的缓存方案(一)
40 0
|
4月前
|
缓存 算法 NoSQL
【分布式详解】一致性算法、全局唯一ID、分布式锁、分布式事务、 分布式缓存、分布式任务、分布式会话
分布式系统通过副本控制协议,使得从系统外部读取系统内部各个副本的数据在一定的约束条件下相同,称之为副本一致性(consistency)。副本一致性是针对分布式系统而言的,不是针对某一个副本而言。强一致性(strong consistency):任何时刻任何用户或节点都可以读到最近一次成功更新的副本数据。强一致性是程度最高的一致性要求,也是实践中最难以实现的一致性。单调一致性(monotonic consistency):任何时刻,任何用户一旦读到某个数据在某次更新后的值,这个用户不会再读到比这个值更旧的值。
409 0
|
21天前
|
存储 缓存 NoSQL
缓存、分布式缓存和持久化
这篇内容介绍了缓存的概念和Redis的作用,以口袋与公文包的比喻解释了缓存如何提高数据访问速度。Redis是一个内存中的高级缓存系统,能提升系统响应速度。接着讨论了为何需要分布式缓存,通过多个“篮子”(Redis节点)解决单点故障和性能瓶颈,保证高可用性和数据安全性。最后提到了Redis的两种持久化机制——RDB(定期数据快照)和AOF(记录写操作日志),分别用照片备份和实时同步来比喻,说明它们在数据丢失风险和恢复速度上的权衡。
|
27天前
|
缓存 NoSQL 数据库
关于高并发下缓存失效的问题(本地锁 && 分布式锁 && Redission 详解)
关于高并发下缓存失效的问题(本地锁 && 分布式锁 && Redission 详解)
30 0
|
2月前
|
缓存 应用服务中间件 数据库
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(多级缓存设计分析)
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(多级缓存设计分析)
38 1
|
6月前
|
缓存 NoSQL 安全
分布式系列教程(05) -分布式Redis缓存 (持久化)
分布式系列教程(05) -分布式Redis缓存 (持久化)
109 0
|
2月前
|
存储 缓存
本地缓存和分布式缓存区别
【2月更文挑战第16天】
42 2
本地缓存和分布式缓存区别
|
2月前
|
存储 缓存 算法
说说什么是本地缓存、分布式缓存以及多级缓存,它们各自的优缺点?
说说什么是本地缓存、分布式缓存以及多级缓存,它们各自的优缺点?
|
4月前
|
分布式计算 大数据 数据处理
Spark RDD(弹性分布式数据集)
Spark RDD(弹性分布式数据集)
|
4月前
|
存储 缓存 大数据
本地缓存和分布式缓存
本地缓存和分布式缓存
68 0

热门文章

最新文章