在前面几篇随笔中,介绍了PostSharp的使用,以及整合MemoryCache,《在.NET项目中使用PostSharp,实现AOP面向切面编程处理》、《在.NET项目中使用PostSharp,使用MemoryCache实现缓存的处理》参数了对PostSharp的使用,并介绍了MemoryCache的缓存使用,可是缓存框架的世界里面,有不少成熟的缓存框架,如MemoryCache、Redis、Memcached、Couchbase、System.Web.Caching等,这时候咱们若是有一个大内总管或者一个吸星大法的武功,把它们融合起来,那么就真的是很是完美的一件事情,这个就是咱们CacheManager缓存框架了,这样的灵活性缓存框架并结合了PostSharp横切面对常规代码的简化功能,简直就是好鞍配好马、宝剑赠英雄,整合起来处理缓存真的是如虎添翼。html
关于这个缓存框架,我在随笔《.NET缓存框架CacheManager在混合式开发框架中的应用(1)-CacheManager的介绍和使用》中进行了介绍,读者能够从中了解一下CacheManager缓存框架到底是一个什么样的东西。git
CacheManager是一个以C#语言开发的开源.Net缓存框架抽象层。它不是具体的缓存实现,但它支持多种缓存提供者(如Redis、Memcached等)并提供不少高级特性。
CacheManager 主要的目的使开发者更容易处理各类复杂的缓存场景,使用CacheManager能够实现多层的缓存,让进程内缓存在分布式缓存以前,且仅需几行代码来处理。
CacheManager 不只仅是一个接口去统一不一样缓存提供者的编程模型,它使咱们在一个项目里面改变缓存策略变得很是容易,同时也提供更多的特性:如缓存同步、并发更新、序列号、事件处理、性能计算等等,开发人员能够在须要的时候选择这些特性。github
CacheManager缓存框架支持Winform和Web等应用开发,以及支持多种流行的缓存实现,如MemoryCache、Redis、Memcached、Couchbase、System.Web.Caching等。redis
纵观整个缓存框架,它的特定很明显,在支持多种缓存实现外,自己主要是之内存缓存(进程内)为主,其余分布式缓存为辅的多层缓存架构方式,以达到快速命中和处理的机制,它们内部有相关的消息处理,使得即便是分布式缓存,也可以及时实现并发同步的缓存处理。数据库
CacheManager缓存框架在配置方面,支持代码方式的配置、XML配置,以及JSON格式的配置处理,很是方便。编程
CacheManager缓存框架默认对缓存数据的序列化是采用二进制方式,同时也支持多种自定义序列化的方式,如基于JOSN.NET的JSON序列化或者自定义序列化方式。缓存
CacheManager缓存框架能够对缓存记录的增长、删除、更新等相关事件进行记录。架构
CacheManager缓存框架的缓存数据是强类型的,能够支持各类常规类型的处理,如Int、String、List类型等各类基础类型,以及可序列号的各类对象及列表对象。并发
CacheManager缓存框架支持多层的缓存实现,内部良好的机制能够高效、及时的同步好各层缓存的数据。框架
CacheManager缓存框架支持对各类操做的日志记录。
CacheManager缓存框架在分布式缓存实现中支持对更新的锁定和事务处理,让缓存保持更好的同步处理,内部机制实现版本冲突处理。
CacheManager缓存框架支持两种缓存过时的处理,如绝对时间的过时处理,以及固定时段的过时处理,是咱们处理缓存过时更加方便。
....
不少特性基本上覆盖了缓存的常规特性,并且提供的接口基本上也是咱们所常常用的Add、Put、Update、Remove等接口,使用起来也很是方便。
CacheManager的GitHub源码地址为:https://github.com/MichaCo/CacheManager,若是须要具体的Demo及说明,能够访问其官网:http://cachemanager.net/。
通常来讲,对于单机版本的应用场景,基本上是无需引入这种缓存框架的,由于客户端的并发量不多,并且数据请求也是寥寥可数的,性能方便不会有任何问题。
若是对于分布式的应用系统,如我在不少随笔中介绍到个人《混合式开发框架》、《Web开发框架》,因为数据请求是并发量随着用户增加而增加的,特别对于一些互联网的应用系统,极端状况下某个时间点一下可能就会达到了整个应用并发的峰值。那么这种分布式的系统架构,引入数据缓存来下降IO的并发数,把耗时请求转换为内存的高速请求,能够极大程度的下降系统宕机的风险。
咱们以基于常规的Web API层来构建应用框架为例,整个数据缓存层,应该是在Web API层之下、业务实现层之上的一个层,以下所示。
因为MemoryCache是在单个机器上进行缓存的处理,并且没法进行序列号,电脑宕机后就会所有丢掉缓存内容,因为这个缺点,咱们对《在.NET项目中使用PostSharp,使用MemoryCache实现缓存的处理》基础上进行进一步的调整,整合CacheManager进行使,从而能够利用缓存弹性化处理以及可序列号的特色。
咱们在正常状况下,仍是须要使用Redis这个强大的分布式缓存的,关于Redis的安装和使用,请参考个人随笔《基于C#的MongoDB数据库开发应用(4)--Redis的安装及使用》。
咱们首先定义一个CacheAttribute的Aspect类,用来对缓存的切面处理。
/// <summary> /// 方法实现缓存的标识 /// </summary> [Serializable] public class CacheAttribute : MethodInterceptionAspect { /// <summary> /// 缓存的失效时间设置,默认采用30分钟 /// </summary> public int ExpirationPeriod = 30; /// <summary> /// PostSharp的调用处理,实现数据的缓存处理 /// </summary> public override void OnInvoke(MethodInterceptionArgs args) { //默认30分钟失效,若是设置过时时间,那么采用设置值 TimeSpan timeSpan = new TimeSpan(0, 0, ExpirationPeriod, 0); var cache = MethodResultCache.GetCache(args.Method, timeSpan); var arguments = args.Arguments.ToList();//args.Arguments.Union(new[] {WindowsIdentity.GetCurrent().Name}).ToList(); var result = cache.GetCachedResult(arguments); if (result != null) { args.ReturnValue = result; return; } else { base.OnInvoke(args); //调用后更新缓存 cache.CacheCallResult(args.ReturnValue, arguments); } } }
而后就是进一步处理完善类 MethodResultCache来对缓存数据进行处理了。该类负责构造一个CacheManager管理类来对缓存进行处理,以下代码所示。
初始化缓存管理器的代码以下所示,这里利用了MemoryCache做为快速的内存缓存(主缓存),以及Redis做为序列化存储的缓存容器(从缓存),它们有内在机制进行同步处理。
/// <summary> /// 初始化缓存管理器 /// </summary> private void InitCacheManager() { _cache = CacheFactory.Build("getStartedCache", settings => { settings .WithSystemRuntimeCacheHandle("handleName") .And .WithRedisConfiguration("redis", config => { config.WithAllowAdmin() .WithDatabase(0) .WithEndpoint("localhost", 6379); }) .WithMaxRetries(100) .WithRetryTimeout(50) .WithRedisBackplane("redis") .WithRedisCacheHandle("redis", true) ; }); }
对缓存结果进行处理的函数以下所示。
/// <summary> /// 缓存结果内容 /// </summary> /// <param name="result">待加入缓存的结果</param> /// <param name="arguments">方法的参数集合</param> public void CacheCallResult(object result, IEnumerable<object> arguments) { var key = GetCacheKey(arguments); _cache.Remove(key); var item = new CacheItem<object>(key, result, ExpirationMode.Sliding, _expirationPeriod); _cache.Add(item); }
首先就是获取方法参数的键,而后移除对应的缓存,加入新的缓存,并设定缓存的失效时间段便可。
清空缓存的时候,直接调用管理类的Clear方法便可达到目的。
/// <summary> /// 清空方法的缓存 /// </summary> public void ClearCachedResults() { _cache.Clear(); }
这样,咱们处理好后,在一个业务调用类里面进行设置缓存标志便可,以下代码所示。
/// <summary> /// 获取用户所有简单对象信息,并放到缓存里面 /// </summary> /// <returns></returns> [Cache(ExpirationPeriod = 1)] public static List<SimpleUserInfo> GetSimpleUsers(int userid) { Thread.Sleep(500); //return CallerFactory<IUserService>.Instance.GetSimpleUsers(); //模拟从数据库获取数据 List<SimpleUserInfo> list = new List<SimpleUserInfo>(); for (int i = 0; i < 10; i++) { var info = new SimpleUserInfo(); info.ID = i; info.Name = string.Concat("Name:", i); info.FullName = string.Concat("姓名:", i); list.Add(info); } return list; }
为了测试缓存的处理,以及对Redis的支持状况,我编写了一个简单的案例,功能以下所示。
测试代码以下所示。
//测试缓存 private void button1_Click(object sender, EventArgs e) { Console.WriteLine(" 测试缓存: "); //测试反复调用获取数值的耗时 DateTime start = DateTime.Now; var list = CacheService.GetSimpleUsers(1); int end = (int)DateTime.Now.Subtract(start).TotalMilliseconds; Console.WriteLine(" first: " + end); Console.WriteLine(" List: " + list.Count); //Second test //检查不一样的方法参数,对缓存值的影响 start = DateTime.Now; list = CacheService.GetSimpleUsers(2); end = (int)DateTime.Now.Subtract(start).TotalMilliseconds; Console.WriteLine(" Second: " + end); Console.WriteLine(" List2: " + list.Count); } //更新缓存 private void button2_Click(object sender, EventArgs e) { Console.WriteLine(" 更新缓存: "); //首先获取对应键的缓存值 //而后对缓存进行修改 //最后从新加入缓存 var key = "CacheManagerAndPostSharp.CacheService.GetSimpleUsers"; var item = MethodResultCache.GetCache(key); var argument = new List<object>(){1}; var result = item.GetCachedResult(argument); Console.WriteLine("OldResult:" + result.ToJson()); List<SimpleUserInfo> newList = result as List<SimpleUserInfo>; if(newList != null) { newList.Add(new SimpleUserInfo() { ID = new Random().Next(), Name = RandomChinese.GetRandomChars(2) }); } item.CacheCallResult(newList, argument); } //清空缓存 private void button3_Click(object sender, EventArgs e) { Console.WriteLine(" 清空缓存: "); //首先获取对应键的缓存值 var key = "CacheManagerAndPostSharp.CacheService.GetSimpleUsers"; var item = MethodResultCache.GetCache(key); var argument = new List<object>(){1}; //而后清空方法的全部缓存 item.ClearCachedResults(); //最后从新检验缓存值为空 var result = item.GetCachedResult(argument); Console.WriteLine("Result:" + result !=null ? result.ToJson() : "null"); }
测试运行结果以下所示。
测试缓存: first: 870 List: 10 Second: 502 List2: 10 更新缓存: OldResult:[
{"ID":0,"HandNo":null,"Name":"Name:0","Password":null,"FullName":"姓名:0","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":1,"HandNo":null,"Name":"Name:1","Password":null,"FullName":"姓名:1","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":2,"HandNo":null,"Name":"Name:2","Password":null,"FullName":"姓名:2","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":3,"HandNo":null,"Name":"Name:3","Password":null,"FullName":"姓名:3","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":4,"HandNo":null,"Name":"Name:4","Password":null,"FullName":"姓名:4","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":5,"HandNo":null,"Name":"Name:5","Password":null,"FullName":"姓名:5","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":6,"HandNo":null,"Name":"Name:6","Password":null,"FullName":"姓名:6","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":7,"HandNo":null,"Name":"Name:7","Password":null,"FullName":"姓名:7","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":8,"HandNo":null,"Name":"Name:8","Password":null,"FullName":"姓名:8","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":9,"HandNo":null,"Name":"Name:9","Password":null,"FullName":"姓名:9","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null}] 测试缓存: first: 0 List: 11 Second: 0 List2: 10 清空缓存: null
同时咱们看到在Redis里面,有相关的记录以下所示。
结合PostSharp和CacheManager,使得咱们在使用缓存方面更具备弹性化,能够根据状况经过配置实现使用不一样的缓存处理,可是在代码中使用缓存就是只须要声明一下便可,很是方便简洁了。