回到目录

戏说当年

大叔原创的分布式数据集缓存在之前的企业级框架里介绍过,大家可以关注《我心中的核心组件(可插拔的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 > )
{
var sb = new StringBuilder();
for (int i = ; 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[].GetType();
foreach (var member in t.GetFields())
{
result += "_" + member.Name + "_" + t.GetField(member.Name).GetValue(arr[]);
}
result = result.Remove(, );
}
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 - );
sb.Append(result.ToString());
}
else//简单值类型处理
{
sb.Append(input.Arguments[i].ToString());
} if (i != input.Arguments.Count - )
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)[];
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, );
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这种中间存储服务器。

aaarticlea/png;base64,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" alt="" />

缓存生产者

缓存生产者与日志,消息等生产者类似,由于全局使用一个实例即可,所以在设计时采用了单例模式,工厂模式,策略模式等,目前在工厂里只有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分布式数据集缓存介绍的更多相关文章

  1. Lind.DDD敏捷领域驱动框架~介绍

    回到占占推荐博客索引 最近觉得自己的框架过于复杂,在实现开发使用中有些不爽,自己的朋友们也经常和我说,框架太麻烦了,要引用的类库太多:之前架构之所以这样设计,完全出于对职责分离和代码附复用的考虑,主要 ...

  2. Lind.DDD.LindAspects方法拦截的介绍

    回到目录 什么是LindAspects 之前写了关于Aspects的文章<Lind.DDD.Aspects通过Plugins实现方法的动态拦截~Lind里的AOP>,今天主要在设计思想上进 ...

  3. Lind.DDD.Messaging框架通讯组件介绍

    回到目录 大 家好,今天有时间来介绍一下Lind.DDD框架里的消息机制,消息发送这块一般的实现方法是将Email,SMS等集成到一个公用类库里,而本身 Email和SMS没什么关系,它们也不会有什么 ...

  4. Lind.DDD.Domain领域模型介绍

    回到目录 Lind.DDD.Domain位于Lind.DDD核心项目中,它主要面向领域实体而设计,由一个IEntity的标识接口,EntityBase基类和N个Entity实体类组成,其中IEntit ...

  5. Lind.DDD.DynamicModules动态模块化的设计

    回到目录 在Lind.DDD框架里有Module,主要用于全局自动添加的模块,它类似于ABP系统里的Module,但有时过于自动化了可能使系统太死板,而有时将需要的模块手动载入可能对我们更合适,所以大 ...

  6. Lind.DDD.Repositories.Redis层介绍

    回到目录 之前已经发生了 大叔之前介绍过关于redis的文章,有缓存,队列,分布式pub/sub,数据集缓存以及仓储redis的实现等等,而今天在Lind.DDD的持久化组件里,redis当然也有一席 ...

  7. [Berkeley]弹性分布式数据集RDD的介绍(RDD: A Fault-Tolerant Abstraction for In-Memory Cluster Computing 论文翻译)

    摘要:     本文提出了分布式内存抽象的概念--弹性分布式数据集(RDD,Resilient Distributed Datasets).它同意开发者在大型集群上运行基于内存的计算.RDD适用于两种 ...

  8. Lind.DDD敏捷领域驱动框架~Lind.DDD各层介绍

    回到目录 Lind.DDD项目主要面向敏捷,快速开发,领域驱动等,对于它的分层也是能合并的合并,比之前大叔的框架分层更粗糙一些,或者说更大胆一些,在开发人员使用上,可能会感觉更方便了,更益使用了,这就 ...

  9. Lind.DDD.ExpressionExtensions动态构建表达式树,实现对数据集的权限控制

    回到目录 Lind.DDD框架里提出了对数据集的控制,某些权限的用户为某些表添加某些数据集的权限,具体实现是在一张表中存储用户ID,表名,检索字段,检索值和检索操作符,然后用户登陆后,通过自己权限来构 ...

随机推荐

  1. Android笔记——permission权限大全

    访问登记属性 android.permission.ACCESS_CHECKIN_PROPERTIES ,读取或写入登记check-in数据库属性表的权限 获取错略位置 android.permiss ...

  2. Java 超简单实现发送邮件(可动态控制发送人数)

    发送邮件的实现 需要事先引入以下几个架包,最重要的架包是jodd-3.7这个 以上架包下载地址:http://pan.baidu.com/s/1kVs7Tyv  提取密码:h22x 新建一个Util类 ...

  3. OpenCASCADE6.8.0 Reference Manual Serach Problem

    OpenCASCADE6.8.0 Reference Manual Serach Problem eryar@163.com 1. Problem 有网友反映OpenCASCADE6.8.0的Refe ...

  4. 传智播客--高级控件--showdialog关闭(小白内容)

    以往我在WPF里,用ShowDialog展示出一个页面,一般都是用Close()进行关闭. 今天看传智播客的视频时,了解到还能直接给DialogResult一个TRUE或者false的属性,使页面关闭 ...

  5. ASP.NET MVC之Session State性能问题(七)

    前言 这一节翻译一篇有关Session State性能问题的文章,非一字一句翻译. 话题 不知道我们在真实环境中是否用到了Session State特性,它主要用来当在同一浏览器发出多个请求时来存储数 ...

  6. java后台搭建学习计划

    1. 使用maven管理java项目 2.linux安装mysql 3.linux安装redis 4. mybatis使用demo 5. cannal使用demo 6. 用spring4开发rest应 ...

  7. scikit-learn 线性回归算法库小结

    scikit-learn对于线性回归提供了比较多的类库,这些类库都可以用来做线性回归分析,本文就对这些类库的使用做一个总结,重点讲述这些线性回归算法库的不同和各自的使用场景. 线性回归的目的是要得到输 ...

  8. 使用karma测试平时写的小demo(arguments为例)

    有人说前端自动化测试非常困难,我觉得确实如此.在项目中,我个人也不放心写的测试,还是要手动测试.但是我们平时写demo学习时,完全可以使用自动化测试. 传统demo 1,新建一个html 2,写入js ...

  9. Oracle常用函数

    前一段时间学习Oracle 时做的学习笔记,整理了一下,下面是分享的Oracle常用函数的部分笔记,以后还会分享其他部分的笔记,请大家批评指正. 1.Oracle 数据库中的to_date()函数的使 ...

  10. Java面向对象练习

    1.定义长方形类,含: 属性:宽.高(整型): 方法:求周长.面积: 构造方法3个:(1)无参——宽.高默认值为1:(2)1个参数——宽.高均为参数值:(3)2个参数——宽.高各为参数值. 要求:进行 ...