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

[ASP.net教程]Lind.DDD.Caching分布式数据集缓存介绍


回到目录

戏说当年

大叔原创的分布式数据集缓存在之前的企业级框架里介绍过,大家可以关注《我心中的核心组件(可插拔的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

回到目录