Orchard源码分析 - 缓存管理
ICacheManager & ICacheHolder
Orchard缓存管理主要通过 ICacheManager 接口对外提供缓存服务. 其实现类DefaultCacheManager的构造函数如下,
DefaultCacheManager(Type component/*此类型是缓存服务消费者的类型名称,在CacheModule中配置注册,
其主要作用是在CacheHolder中创建一个cacheKey,cacheKey使用三元组(Tuple)数组结构,使cacheKey免于冲突,(component类型+用户Key类型+返回值类型)保证cacheKey的唯一性*/
, ICacheHolder cacheHolder)
ICache对象[最小缓存单元]由DefaultCacheHolder类提供,此类内部使用一个线程安全的ConcurrentDictionary来缓存Cache对象[Cache对象为最小缓存单元],此字典就是Orchard缓存的最终存储位置 !
此字典使用三元组(Tuple)做为cache避免key冲突. 获取Cache的原理是:
1. 如果在此字典中用cacheKey[上面说的三元组结构]取到值则直接返回.
2. 如果未取到则new Cache .(此处硬编码写死了ICache的实现类,如果想使用其它Cache实现则必须修改此处)
public ICache<TKey, TResult> GetCache<TKey, TResult>(Type component) {
var cacheKey = new CacheKey(component, typeof(TKey), typeof(TResult));
var result = _caches.GetOrAdd(cacheKey, k => new Cache<TKey, TResult>(_cacheContextAccessor));
return (Cache<TKey, TResult>)result;
}
ICache (最小缓存单元)
此接口的实现类是Orchard中的一个最小缓存单元. 但此缓存单元并不是直接拿来使用的object,而是需要调用Get(key, acquire)才能获取想要的缓存值 .
Cache类同样使用了线程安全的ConcurrentDictionary来存储键值数据,和DefaultCacheHolder不同的是: 一个Cache对象中,此字典仅有一对键值项.
Cache类Get缓存值的原理是 :
1. 如果用key在ConcurrentDictionary中未取到值,那么将创建一个CacheEntry
2. 如果用key在ConcurrentDictionary中取到值,则更新此CacheEntry
private CacheEntry UpdateEntry(CacheEntry currentEntry, TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
//在此检测currentEntry中令牌列表中是否有任一令牌过期,如果有一个过期了,则重建CacheEntry,否则CacheEntry保持原样.
var entry = (currentEntry.Tokens.Any(t => t != null && !t.IsCurrent)) ? CreateEntry(k, acquire) : currentEntry;
PropagateTokens(entry);
return entry;
}
注意此key不是上面说的三元组结构的Key,而是用户定义的泛型key !
CacheEntry是一个Cache的内部私有类,CacheEntry中的Result属性为最终有用的值,Cache.Get方法的返回结果就是Result. (千呼万唤始出来..... 很不容易).
CacheEntry的作用
Orchard缓存的最终值被包装在CacheEntry中,此类内部维护了一个IVolatileToken链表,IVolatileToken是一个过期令牌(内部有一个IsCurrent属性,如果为false则过期,实施过期的代码在UpdateEntry方法中),
1.创建CacheEntry对象时,将回调acquire(context)方法来设置Result属性(即最终缓存值). 这里面要注意context参数,context参数是随着CacheEntry实例创建时一个新的AcquireContext实例,
然后通过AcquireContext构造函数做了一个重要的操作,伪代码为:
context.Monitor=CacheEntry.AddToken
使用缓存的时候,可以调用 Monitor 来将令牌添加到内部链表中.(以下代码风格在Orchard中极为常见)
public IEnumerable<StereotypeDescription> GetStereotypes() {
return _cacheManager.Get("ContentType.Stereotypes", context => {
//此处调用Monitor方法的本质是在对应的CacheEntry中的令牌列表中添加一个令牌项
context.Monitor(_clock.WhenUtc(_clock.UtcNow.AddMinutes(1)));
return _providers.SelectMany(x => x.GetStereotypes());
}); }
2.当回调完成后,将调用CacheEntry.CompactTokens方法来将过期令牌去重
如何创建过期令牌
namespace Orchard.Caching {
//所有实现此空接口的类都被视为令牌提供器
public interface IVolatileProvider : ISingletonDependency {
}
}
注: 在1.8版本及以前版本 IVolatileProvider接口未被明显使用, 仅标识类型. (将来的版本中可能会利用此空接口做一些自动注入的操作,目前版本仍然是手工注入)
在\Environment\OrchardStart.cs类中注册了所有提供器
RegisterVolatileProvider<WebSiteFolder, IWebSiteFolder>(builder);
RegisterVolatileProvider<AppDataFolder, IAppDataFolder>(builder);
RegisterVolatileProvider<DefaultLockFileManager, ILockFileManager>(builder);
RegisterVolatileProvider<Clock, IClock>(builder);
RegisterVolatileProvider<DefaultDependenciesFolder, IDependenciesFolder>(builder);
RegisterVolatileProvider<DefaultExtensionDependenciesManager, IExtensionDependenciesManager>(builder);
RegisterVolatileProvider<DefaultAssemblyProbingFolder, IAssemblyProbingFolder>(builder);
RegisterVolatileProvider<DefaultVirtualPathMonitor, IVirtualPathMonitor>(builder);
RegisterVolatileProvider<DefaultVirtualPathProvider, IVirtualPathProvider>(builder);
分析一个最简单的Signals(信号量)
这是一个最具代表性的过期令牌提供器,在内部使用一个Dictionary来保存所有<键,Token>项,(因为可能有多个服务消费者,但是此类是单例).
1. 当调用 When<T>(T signal) 时,在Dictionary中查找或添加一个Token实例, 用以启用信号量监控.(Token为内部类,实现IVolatileToken,接口. 初始时IsCurrent=true),
此Token被添加到CacheEntry的内部链表中 ,因为是引用类型,当Signals内的字典中的值改变的时候,CacheEntry中的链表值也会改变(引用相同对象, .NET基础知识).
_cacheDuration = _cacheManager.Get("CacheSettingsPart.Duration",
context => {
//监视一个信号,如果此信号被Trigger(触发),则当前令牌被标识过期
context.Monitor(_signals.When(CacheSettingsPart.CacheKey));
return _workContext.CurrentSite.As<CacheSettingsPart>().DefaultCacheDuration;
}
);
2. 当程序另一部分调用此类Trigger方法时,将在Dictionary移除指定的Token并且设置此Token的IsCurrent=false, 标识过期.
//标识令牌过期
_signals.Trigger("MediaProcessing_Saved_" + filter.ImageProfilePartRecord.Name);
引用相等的示例( 基础知识 )
void Main()
{
var a=new Person{N="我是a"};
var b=new Person{N="我是b"};
var c=new Person{N="我是c"};
var dic=new Dictionary<string,object>();
dic.Add("a",a);
dic.Add("b",b);
dic.Add("c",c);
var list=new List<Object>();
list.Add(a);
list.Add(b);
list.Add(c);
object o;
dic.TryGetValue("a",out o);
((Person)o).N="你好";
//字典中与链表中的值 引用是相等的
Console.Write(System.Object.ReferenceEquals(o,list[0])); //输出 True
//因此改变字典中的值, 链表的值也会改变
Console.Write(list[0].N); //输出 "你好"
}
class Person
{
public string N;
}
IAsyncTokenProvider(异步令牌提供器)
DefaultAsyncTokenProvider类内部有个子类 AsyncVolativeToken : IVolatileToken , 在获取令牌(GetToken)时 , 传递一个Action<Action<IVolatileToken>> task参数 ,
此 task将在线程池中异步执行, 将token => _taskTokens.Add(token) 作为参数传递给方法体
public void QueueWorkItem() {
// Start a work item to collect tokens in our internal array
ThreadPool.QueueUserWorkItem(state => {
try {
_task(token => _taskTokens.Add(token));
}
catch (Exception e) {
Logger.Error(e, "Error while monitoring extension files. Assuming extensions are not current.");
_taskException = e;
}
finally {
_isTaskFinished = true;
}
});
}
extensionEntry = _cacheManager.Get(extensionId, ctx => {
var entry = BuildEntry(extensionDescriptor);
if (entry != null) {
ctx.Monitor(_asyncTokenProvider.GetToken(monitor => {
//此方法体将在线程池中异步执行
//monitor调用时将会向AsyncVolativeToken实例的内部的List<IVolatileToken>中添加token
foreach (var loader in _loaders) {
loader.Monitor(entry.Descriptor, token => monitor(token));
}
}));
}
return entry;
});
在缓存获取方法中使用并行
先来看看什么是缓存获取方法:
_cacheManager.Get("CacheSettingsPart.Duration",
context => {
//这个方法仅在Cache中ConcurrentDictionary字典中未找到缓存项才执行,
//我称此方法体为[ 缓存获取方法体 ]
return XXXXXX;
}
);
如果需要在此方法中使用并行执行提高程序效率,那么Orchard 提供了一个IParallelCacheContext 接口,其中有个方法为
IEnumerable<TResult> RunInParallel<T, TResult>(IEnumerable<T> source, Func<T, TResult> selector);
此方法可以独立使用, 也可以在缓存获取方法中使用.
-----------------------------------------
此方法是将 T类型的source序列 中的元素通过selector方法投影到新的 TResult类型对象中 .
T -> 使用PLINQ 并行执行 –> TResult
具体过程是:
1. 首先判断Disabled属性是否为禁用状态,如果禁用则用 LINQ直接投影,并不会并行执行
2. 通过内部公开类 TaskWithAcquireContext 包装原始序列中的所有元素, 构造函数中传递投影元素的 lamada
然后用第二步中的投影结果 再 并行执行, 每个元素都会调用 Execute方法
// Run tasks in parallel and combine results immediately
var result = tasks
.AsParallel() // prepare for parallel execution
.AsOrdered() // preserve initial enumeration order
.Select(task => task.Execute()) // prepare tasks to run in parallel
.ToArray(); // force evaluation
那么调用Execute方法会做什么事情呢,下面我们要分两种情况:
1. 在缓存获取方法中使用RunInParallel
我们可以看到,如果在缓存方法体里面调用RunInParallel, _cacheContextAccessor.Current是有值的, _cacheContextAccessor.Current.Monitor 指向当前CacheEntry中的AddToken方法.
未完待续。。。。。。。
Orchard源码分析 - 缓存管理的更多相关文章
- MyBatis 源码分析 - 缓存原理
1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...
- TOMCAT8源码分析——SESSION管理分析(上)
前言 对于广大java开发者而已,对于J2EE规范中的Session应该并不陌生,我们可以使用Session管理用户的会话信息,最常见的就是拿Session用来存放用户登录.身份.权限及状态等信息.对 ...
- Tomcat源码分析——Session管理分析(下)
前言 在<TOMCAT源码分析——SESSION管理分析(上)>一文中我介绍了Session.Session管理器,还以StandardManager为例介绍了Session管理器的初始化 ...
- Tomcat源码分析——Session管理分析(上)
前言 对于广大java开发者而已,对于J2EE规范中的Session应该并不陌生,我们可以使用Session管理用户的会话信息,最常见的就是拿Session用来存放用户登录.身份.权限及状态等信息.对 ...
- Orchard源码分析(5):Host相关(Orchard.Environment.DefaultOrchardHost类)
概述 Host 是应用程序域级的单例,代表了Orchard应用程序.其处理应用程序生命周期中的初始化.BeginRequest事件.EndRequest事件等. 可以简单理解为HttpApplicat ...
- Orchard源码分析(1):Orchard架构
本文主要参考官方文档"How Orchard works"以及Orchardch上的翻译. 源码分析应该做到庖丁解牛,而不是以管窥豹或瞎子摸象.所以先对Orchard架构有 ...
- Orchard源码分析(6):Shell相关
概述在Orchard中,提出子站点(Tenant)的概念,目的是为了增加站点密度,即一个应用程序域可以有多个子站点. Shell是子站点(Tenant)级的单例,换句话说Shell代表了子站点.对比来 ...
- Orchard源码分析(5.1):Host初始化(DefaultOrchardHost.Initialize方法)
概述 Orchard作为一个可扩展的CMS系统,是由一系列的模块(Modules)或主题(Themes)组成,这些模块或主题统称为扩展(Extensions).在初始化或运行时需要对扩展进行安装:De ...
- Orchard源码分析(4.4):Orchard.Caching.CacheModule类
概述 CacheModule也是一个Autofac模块. 一.CacheModule类 CacheModule将DefaultCacheManager注册为ICacheManager: ...
随机推荐
- on where having总结
1. ON 和WHERE 所有的查询都回产生一个中间临时报表,查询结果就是从返回临时报表中得到.ON和WHERE后面所跟限制条件的区别,主要与限制条件起作用的时机有关, ON根据限制条件对数据库记录进 ...
- 使用 dlv 调试go 程序
目录 使用 dlv 调试smartraiden 一 正常启动 smartraiden 二 dlv 调试 三 dlv attach 使用 dlv 调试smartraiden by 白振轩 使用 dlv ...
- “全栈2019”Java第二十七章:流程控制语句中循环语句for
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- react学习之路-配制antd-mobile
经过将近三个月的使用,现在终于在我老大的带领下做完了一个react的项目,感觉还可以,最大的不足就是,对于react中的很多的东西都是掺杂着jq使用来做的,这是最不满意的一点吧,但是开发进度很近,只能 ...
- 让子类使用父类的Logger
参考博客:https://blog.csdn.net/zx1323/article/details/71262613 1.让子类使用父类的Logger,这是一种语法思路,可以减少代码臃肿. 2.使用的 ...
- [ActionScript 3.0] AS3 socket示例(官方示例)
下例对套接字执行读写操作,并输出在套接字事件期间传输的信息. 该示例的要点遵循: 该构造函数创建名为 socket 的 CustomSocket 实例,并将主机名 localhost 和端口 80 作 ...
- ArchLinux借助Winetricks-zh安裝WineQQ8.1
Wine是一个在x86.x86-64上容许类Unix操作系统在X Window System下运行Microsoft Windows程序的软件.Wine有另一个非官方名称,"Windows ...
- HDU 5442 后缀自动机(从环字符串选定一个位置 , 时针或顺时针走一遍,希望得到字典序最大)
http://acm.hdu.edu.cn/showproblem.php?pid=5442 题目大意: 给定一个字符串,可理解成环,然后选定一位置,逆时针或顺时针走一遍,希望得到字典序最大,如果同样 ...
- rocketmq sql解析过滤
activemq rocketmq kafka robbitmq 公司 apache alibaba LinkedIn Pivotal 编写语言 java Erlang 客户端支持 其他协议支持 mq ...
- 在线词云制作tagxedo
最近在用python制作词云的时候发现了一个更加方便快捷很好玩的词云制作网站 http://www.tagxedo.com/app.html 所以今天就来大致介绍下是怎么使用的 1.先来介绍下tagx ...