Orchard 刨析:Caching
关于Orchard中的Caching组件已经有一些文章做了介绍,为了系列的完整性会再次对Caching组件进行一次介绍。
缓存的使用
在Orchard看到如下一段代码:
可以看到使用缓存的方法Get而看不到以前常见的缓存方法:AddSetRemove,是不是很神奇。
其实也不是那么的神奇,Get方法中根据传入的Key(culture)在缓存数据存储中搜索,如果存在则直接返回缓存结果,如果不存在则执行传入的委托,并把委托返回的结果放入到缓存中。
那么怎么确保缓存是不是失效呢?难道不支持吗?
Orchard的缓存中比较难理解的就是这一块了,Orchard的缓存失效机制非常的赞。
ctx.Monitor(_signals.When("culturesChanged"));
这一块就是缓存失效的精髓,这边暂把它叫做添加一个可监控的挥发令牌。这个When方法其实返回了一个实现了IVolatileToken接口的对象实例。下面会有详细的说明。
接口关系
下面是缓存的基本接口的依赖关系
ICache<TKey,TResult>
ICache<TKey,TResult>是一个泛型接口,根据泛型名称可以看出通过该接口可以定义缓存Key的类型和缓存结果的类型,也就是说Orchard中缓存的Key不只是大家所常见String类型。
ICache接口是缓存组件中最终的接口,可以这么理解ICacheManager、ICacheHolder都是缓存组件为了可扩展性和易用性而设立的抽象,ICache才是缓存的实现(存储缓存数据的地方)。
下面我们来看一看它的实现类型:Cache<TKey,TResult>。
IVolatileToken
一个抽象的挥发令牌是缓存状态的一个关键接口,里面的IsCurrent含义是是否是当前的对象,如果为false则代表缓存失效,如果为true则代表缓存有效。这边大家先了解下概念后面会详细说明。
字段
ICacheContextAccessor _cacheContextAccessor:缓存上下文访问器。
ConcurrentDictionary<TKey, CacheEntry> _entries:线程安全的字典表,用于存储缓存的数据。
类
CacheEntry 缓存条目,对缓存结果进行了封装,主要对缓存结果添加了令牌机制(IVolatileToken)。
TResult Result:缓存的数据。
IEnumerable<IVolatileToken> Tokens:这个缓存条目所对应的挥发令牌(一个缓存可由多种情况导致缓存失效,如:5分钟之后失效、数据被更改时候失效等多种失效方式)。
AddToken(IVolatileToken volatileToken):添加一个新的令牌至Tokens。
CompactTokens():主要用于去除Tokens中重复的令牌。(因为令牌是提供给外部添加的所有可能会出现重复的令牌,为提高性能(令牌内的执行执行时间不得而知)需要剔除重复的令牌)
接口方法
可以看到简单的Cache类中包含了添加缓存、更新缓存的方法,而Get方法就是对这两个方法的封装,我们来看看Get方法的实现
代码很简单,使用了ConcurrentDictionary字典的特性添加或者更新(当Key不存在时执行第一个委托内的方法:AddEntrty,当key存在时执行第二个委托内的方法UpdateEntry)
私有方法
PropagateTokens
可以看作探测缓存是否失效。CreateEntry
AddEntry
UpdateEntry
注意看(currentEntry.Tokens.Any(t => t != null && !t.IsCurrent))缓存失效的核心就在这里了,实现了IVolatileToken接口的对象是一个引用类型,只要这个实例被添加至对应的缓存条目,并且通过一些手段将IsCurrent设为False那么这个缓存就失效了。
ICacheHolder(生命周期:租户单例)
顾名思义这个接口主要是用来维护一个ICache接口集合的。
ICacheHolder中包含了一个方法
对应的实现
为什么需要ICacheHolder?
因为缓存Key是很容易冲突的,比如一名开发人员在开发管理员模块的时将所有的管理员账户信息缓存起来那么缓存Key很可能为Users,那么另外一名开发人员在开发会员模块时候也很有可能会使用Users这个人见人爱的Key,这时候缓存中的数据就冲突了。不过在Orchard中很好的避免了这个问题,就是分区。
在ICacheHolder中维护了一个ConcurrentDictionary<CacheKey, object>字典表,CacheKey为一个三元组,类型全是Type,分别为:使用缓存的组件类型,缓存Key类型,缓存结果类型。
以使用缓存的服务类、Key类型、结果类型作为一个Key,也就是说开发人员只要保证在同一个类型中不使用相同类型相同值的Key和相同的结果类型,该缓存就不会冲突(笔者觉得这个创意非常的赞)。
ICacheManager(生命周期:瞬态)
缓存管理者接口,也是大家最经常使用的接口。(ICacheManager的注入在上一篇“Orchard 刨析:前奏曲”中有解释。)
字段
Type _component:使用缓存的服务组件类型。
ICacheHolder _cacheHolder:缓存持有者。
方法
ICache<TKey, TResult> GetCache<TKey, TResult>():从缓存持有者中获取一个缓存。
TResult Get<TKey, TResult>(TKey key, Func<AcquireContext<TKey>, TResult> acquire):调用GetCache<TKey,TResult>方法,并且调用ICache的Get方法。
也包含了两个方法,主要的方法是GetCache这一个,Get方法只是对这个方法进行了封装,笔者觉得这种还是使用扩展方法来实现会更好。
下面我们来看看实现。
非常的简单。
拿ISignals(生命周期:全局单例)开刀
ISignals是一个以信号量方式提供的一个简单的挥发提供者。
ISignals接口提供了两个方法一个用来生成Token,一个用来使Token失效(设置IsCurrent为false)。
我们来看内部实现
字段
IDictionary<object, token=""><object,Token> _tokens:用来存储信号量令牌的字典表。
方法
void Trigger<T>(T signal); 触发一个信号量(设置IsCurrent为false导致缓存失效)。
IVolatileToken When<T>(T signal);(生成一个令牌)
可以看到这么一个简单的类就可以实现一个缓存失效机制,可见Orchard的缓存失效机制是易于扩展的,Orchard中还内置了一些其他的失效机制实现,如:时间、文件监控等。
总结
以上是对Orchard缓存的一个简单说明,一个简简单单的缓存都被Orchard设计的这么华丽,不得不敬佩Microsoft的工程师们,下面我们在简单的过一遍缓存的流程。
异步令牌提供者
IAsyncTokenProvider
在上面也说了为什么需要压缩缓存条目中的令牌,原因就是会影响性能,所以Orchard也提供了异步的令牌机制,主要是以异步的方式传播令牌,实现方式也非常简单,用到了线程池。
并行缓存
并行缓存暂时不提供说明,因为还有一些争议:并行缓存是不是属于缓存。
写在最后
刚开始写这个系列没多久,可能开头的文章逻辑并不是很清楚,期待后续文章的改善,如果在读文中有遇到问题请移步QQ群:299744835,专为本系列提供的一个交流探讨的地方。