Orchard 代码分析 - 1 缓存管理

Orchard 官方网站:http://www.orchardproject.net/

Orchard 中文网站:http://www.orchardch.com/

官网上并没有找到代码的具体说明,中文网站上的资源也不多。主要参考了以下网络文档:

http://www.cnblogs.com/ants/p/3736538.html

http://www.cnblogs.com/n-pei/archive/2011/05/01/2033911.html

废话不多说,直接开始。


首先,我脑中先勾勒出一个标准的缓存管理模块:

1、使用 ConcurrentDictionary 这个线程安全的字典存储缓存的 Key 和 Result,并用 Static 包装,单例提供;

2、CacheManager :缓存管理的接口模块,提供 Add、Remove、Update 方法,分别对应缓存的增加、删除和更新;

我曾经很多次使用这种简单的缓存管理方式,使用过程中发现一些问题:

1、Key 是唯一的,所以我在添加缓存的时候要小心维护,如果 Key 重复了则无法插入缓存字典;

2、我必须自己维护数据的失效,如果一个人开发还好,如果多人开发的话,除了编写很完善的文档,没有什么好的办法;

3、操作起来很麻烦,要写很多代码........... 数据的更改要Update,新增要 Add,Remove倒是很少用,但经常忘记写.......毕竟和很多逻辑裹挟在一起......


接下来看一下 Orchard 的实现方式,如果能解决我的问题最好..........


我看代码的习惯是从用法开始,正好我下载的 Orchard v1.8 有非常不错的 Test,于是从这儿开始:

Orchard.Framework.Tests \ Caching \ CacheTests.cs


首先看这段代码,这段代码向缓存管理器(_cacheManager)中增加了一条缓存记录。


如果F12查看 ICacheManager 的话,可以发现缓存管理中只用到了两个方法:


Get 方法返回一个缓存结果, GetCache 返回一个缓存存储器。而且,进一步查询 DefaultCacheManager(实现 ICacheManager 接口)类,发现 Get 方法调用了 GetCache 方法,并继续调用 GetCache 方法返回的 ICache 对象的 Get 方法,获取缓存。

继续挖,下面是 Cache类的 Get 方法:


_entries 是一个线程安全的字典,这里使用了它的一个方法,AddOrUpdate,这就解决了缓存的添加和更新。

这里有个让我头疼的参数 Func<AcquireContext<TKey>,TResult> acquire 这个参数从 DefaultCacheManager.Get 一直传递到这儿,还没有发生作用,到底干嘛的?

继续挖, Cache.AddEntry 和 Cache.UpdateEntry,再挖到 CreateEntry ,终于发现了acquire 开始使用了。


acquire是一个匿名委托,常表现为 Lamda表达式,输入 AcquireContext<TKey>作为参数,输出 TResult。在这个测试中, 对应的参数是 ctx=>"testResult",显然,这只是传入一个字符串作为 TResult。

使用委托是一个高明的办法,但是我对委托的掌握不是很好,所以............ 再看看有多少种用法。

回到 DefaultCacheManager ,对 Get 方法 Find Usages:

Sample-1:


这里的 Lamda 表达式使用了 AcquireContext .Monitor 属性(又是一个委托),一路追踪代码。上一张图中的CreateEntry 中创建了一个新的名为 context 的 AcquireContext,并将 Monitor 属性赋值为 Entry.AddToken ,将context 带入到这个 Lamba,就将 _signal.When(CacheSettingsPort.CacheKey) 返回的 Token(实现IVolatileToken)通过 Entry.AddToken 委托,添加到了 entry .Tokens (以为维护 Token 的列表)。然后执行下面的 return ...... 

呃......... 确实和备注说的一样,这样的话,第一运行上面代码的时候,会从 return ....... 后面的语句获取 TResult,并存入缓存。第二次开始就直接从缓存读取了。很简单,很聪明,但是很........纠结。

Sample-2:极端的用法


这里用到了Ruby脚本.........  强大得不忍直视..........

至此,我所担心的第3个问题迎刃而解, Orchard 的缓存机制非常强大、非常灵活,编写的代码量也非常小 ........但是,在编写这些代码之前需要考虑的时间似乎会更长.....习惯了就好。

到现在为止,我们追踪了缓存创建、更新的全步骤,再回头看看这些缓存是如何组织的。

回到 DefaultCacheManager,这个类的构造函数需要一个 Component 类型作为参数,干嘛要 Component 的类型呢?

上文提到了 DefaultCacheHolder 这个单例类,我们知道所有的 Cache 都是存储在这个类里的,翻翻它的 Key ,一切明朗起来了:


原来,DefaultCacheHolder 中的 Key 是一个三元组,由 Component类型、Key类型、Result类型组成。这就解释了 DefaultCacheManager 为什么要用 Component 类型来初始化。使用这个三元组的 Key 的好处是,可以为每个 Component 创建对应 Cache,而且用 Key 类型和 Result 类型进行区分,这样就防止了 Key 重复的情况 ...............

虽然不能说聪明,但是很实用,我担心的第1个问题也解决了。


现在源代码里还有一个 DefaultCacheContextAccessor 还没有分析,首先看到的是 [ThreadStatic],这个类的字段 _threadInstance 在每个线程唯一,不共享,还是静态的......。然后看看使用方法,虽然在 DefaultCacheHolder 中作为构造参数传入,但是真正发挥作用是在 Cache 类中 ,作为 Cahe类的构造参数传入,并在 Cache 类中标识当前操作的 IAcquireContext。

我的理解是 DefaultCacheHolder 维护着一个字典,Key上面说了是个三元组,Value就是Cache,而且是单例的。

在访问 DefaultCacheHolder 的Cache的时候,每个Cache对应一个线程和该线程上的 DefaultCacheContextAccessor访问器,所有对 Cache 的请求都通过这个访问器排队提交。这样就提升了缓存管理的性能。---- 这个理解不知道对不对,还需要跑跑代码看看。


最后,要找找缓存失效的机制。我找到了 Signals。它有一个Tigger方法,可以维护Token.IsCurrent = false,再配合看看 Cache.UpdateEntry方法,基本上可以断定 Signals.Trgger方法的作用就是使缓存失效。


现在大部分的问题都解决了,可以总结一下了:

Orchard 的缓存模块确实比较强大,不仅可以缓存常规的数据,还可以缓存委托、脚本、代码片段......这实际上已经超出了常见缓存模块的功能。

当然,强大的同时,也带来了结构复杂的问题。在这里说明一下,Orchard 的 Caching 模块与其它模块耦合程度很低,可以直接拉出来放到你的项目里使用。

很久以来一直在微软的平台上工作,看过很多微软的代码,但是觉得最近一些年,微软的代码越来越难懂了......... 是自己老了?还是微软的编码风格变华丽了?




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值