上一篇文章介绍了Orchard中的缓存,本篇主要针对CacheManager进行分析,CacheManager在Orchard中用于存储应用程序的配置信息以及框架内部的一些功能支持,包括整个拓展及拓展监控都是基于Cache Manager的。Orchard的中的CacheManager也是非常有特色,仅提供了一个Get接口,缓存的过期是通过IVolatileToken接口实现的。
先看一下和CacheManager的接口定义:
1 public interface ICacheManager {
2 TResult Get<TKey, TResult>(TKey key, Func<AcquireContext<TKey>, TResult> acquire);
3 ICache<TKey, TResult> GetCache<TKey, TResult>();
4 }
5
6 public static class CacheManagerExtensions {
7 public static TResult Get<TKey, TResult>(this ICacheManager cacheManager, TKey key, bool preventConcurrentCalls, Func<AcquireContext<TKey>, TResult> acquire) {
8 if (preventConcurrentCalls) {
9 lock(key) {
10 return cacheManager.Get(key, acquire);
11 }
12 }
13 else {
14 return cacheManager.Get(key, acquire);
15 }
16 }
17 }
从上面代码可以看出来,它仅有个Get方法是通过一个Key和一个acquire委托实现的,Key代表缓存标识,acquire代表一个获取实际值的方法,换句话说通过Key在缓存中查找对象,如果找不到从acquire中获取。acquire接受一个以AcquireContext<TKey>为参数的委托。
用下面代码做下测试:
1 public class MyController : Controller
2 {
3 private ICacheManager _cacheManager;
4 public MyController(ICacheManager cacheManager)
5 {
6 _cacheManager = cacheManager;
7 }
8
9 public ActionResult Index()
10 {
11 var time1 = _cacheManager.Get("Time", ctx => { return DateTime.Now.ToString(); });
12 Thread.Sleep(1000);
13 var time2 = _cacheManager.Get("Time", ctx => { return DateTime.Now.ToString(); });
14 return View();
15 }
16 }
最后发现time1和time2的结果一致,证明第一次获取缓存的时候是通过后面的方法创建的,获取了当前的时间,第二次的值和第一次一样,证明是从缓存中取出的。
但是要如何让缓存过期?接下来看一个完整的缓存用法:
public class MyController : Controller
{
private ICacheManager _cacheManager;
private IClock _clock;
public MyController(ICacheManager cacheManager, IClock clock)
{
_cacheManager = cacheManager;
_clock = clock;
}
public ActionResult Index()
{
var time1 = _cacheManager.Get("Time", ctx => {
ctx.Monitor(_clock.When( TimeSpan.FromSeconds(5)));
return DateTime.Now.ToString();
});
Thread.Sleep(1000);
var time2 = _cacheManager.Get("Time", ctx => {
ctx.Monitor(_clock.When(TimeSpan.FromSeconds(5)));
return DateTime.Now.ToString();
});
Thread.Sleep(7000);
var time3 = _cacheManager.Get("Time", ctx => {
ctx.Monitor(_clock.When(TimeSpan.FromSeconds(5)));
return DateTime.Now.ToString();
});
return View();
}
}
上面代码的结果就是,time1 = time2 != time3。 因为time3在获取缓存时已经过期了,所以返回了后面的时间。
相比最开始的代码仅多了一个ctx.Monitor的调用就是AcquireContext<TKey>这个参数起的作用,它是如何实现的?
1 public interface IAcquireContext
2 {
3 Action<IVolatileToken> Monitor { get; }
4 }
5
6 public class AcquireContext<TKey> : IAcquireContext
7 {
8 public AcquireContext(TKey key, Action<IVolatileToken> monitor)
9 {
10 Key = key;
11 Monitor = monitor;
12 }
13
14 public TKey Key { get; private set; }
15 public Action<IVolatileToken> Monitor { get; private set; }
16 }
看上面代码可知AcquireContext(获取上下文)用于保存、映射缓存Key和它的监控委托。监控委托是一个返回值为IVolatileToken的方法。
public interface IVolatileToken {
bool IsCurrent { get; }
}
IVolatileToken真正用来判断当前这个Key是否过期。
接下来看一下上面代码使用的Clock:
1 /// <summary>
2 /// Provides the current Utc <see cref="DateTime"/>, and time related method for cache management.
3 /// This service should be used whenever the current date and time are needed, instead of <seealso cref="DateTime"/> directly.
4 /// It also makes implementations more testable, as time can be mocked.
5 /// </summary>
6 public interface IClock : IVolatileProvider
7 {
8 /// <summary>
9 /// Gets the current <see cref="DateTime"/> of the system, expressed in Utc
10 /// </summary>
11 DateTime UtcNow { get; }
12
13 /// <summary>
14 /// Provides a <see cref="IVolatileToken"/> instance which can be used to cache some information for a
15 /// specific duration.
16 /// </summary>
17 /// <param name="duration">The duration that the token must be valid.</param>
18 /// <example>
19 /// This sample shows how to use the <see cref="When"/> method by returning the result of
20 /// a method named LoadVotes(), which is computed every 10 minutes only.
21 /// <code>
22 /// _cacheManager.Get("votes",
23 /// ctx => {
24 /// ctx.Monitor(_clock.When(TimeSpan.FromMinutes(10)));
25 /// return LoadVotes();
26 /// });
27 /// </code>
28 /// </example>
29 IVolatileToken When(TimeSpan duration);
30
31 /// <summary>
32 /// Provides a <see cref="IVolatileToken"/> instance which can be used to cache some
33 /// until a specific date and time.
34 /// </summary>
35 /// <param name="absoluteUtc">The date and time that the token must be valid until.</param>
36 /// <example>
37 /// This sample shows how to use the <see cref="WhenUtc"/> method by returning the result of
38 /// a method named LoadVotes(), which is computed once, and no more until the end of the year.
39 /// <code>
40 /// var endOfYear = _clock.UtcNow;
41 /// endOfYear.Month = 12;
42 /// endOfYear.Day = 31;
43 ///
44 /// _cacheManager.Get("votes",
45 /// ctx => {
46 /// ctx.Monitor(_clock.WhenUtc(endOfYear));
47 /// return LoadVotes();
48 /// });
49 /// </code>
50 /// </example>
51 IVolatileToken WhenUtc(DateTime absoluteUtc);
52 }
View Code
上面IClock接口的定义还附带了很详细的注释。
1 public class Clock : IClock {
2 public DateTime UtcNow {
3 get { return DateTime.UtcNow; }
4 }
5
6 public IVolatileToken When(TimeSpan duration) {
7 return new AbsoluteExpirationToken(this, duration);
8 }
9
10 public IVolatileToken WhenUtc(DateTime absoluteUtc) {
11 return new AbsoluteExpirationToken(this, absoluteUtc);
12 }
13
14 public class AbsoluteExpirationToken : IVolatileToken {
15 private readonly IClock _clock;
16 private readonly DateTime _invalidateUtc;
17
18 public AbsoluteExpirationToken(IClock clock, DateTime invalidateUtc) {
19 _clock = clock;
20 _invalidateUtc = invalidateUtc;
21 }
22
23 public AbsoluteExpirationToken(IClock clock, TimeSpan duration) {
24 _clock = clock;
25 _invalidateUtc = _clock.UtcNow.Add(duration);
26 }
27
28 public bool IsCurrent {
29 get {
30 return _clock.UtcNow < _invalidateUtc;
31 }
32 }
33 }
34 }
现在已经大致可以猜出ICacheManager的过期是通过获取上下文中存储的这个IVolatileToken判断的。
最后来看一下CacheManager是如何存储缓存的,这里有一个疑问就是AcquireContext是如何存储的?如果不存储这个上下文,那么单纯的缓存对象就无法完成过期判断。
DefaulteCacheManager & CacheHolder:CacheHolder为实际的缓存存储介质。
1 public class DefaultCacheHolder : ICacheHolder {
2 private readonly ICacheContextAccessor _cacheContextAccessor;
3 private readonly ConcurrentDictionary<CacheKey, object> _caches = new ConcurrentDictionary<CacheKey, object>();
4
5 public DefaultCacheHolder(ICacheContextAccessor cacheContextAccessor) {
6 _cacheContextAccessor = cacheContextAccessor;
7 }
8
9 class CacheKey : Tuple<Type, Type, Type> {
10 public CacheKey(Type component, Type key, Type result)
11 : base(component, key, result) {
12 }
13 }
14
15 /// <summary>
16 /// Gets a Cache entry from the cache. If none is found, an empty one is created and returned.
17 /// </summary>
18 /// <typeparam name="TKey">The type of the key within the component.</typeparam>
19 /// <typeparam name="TResult">The type of the result.</typeparam>
20 /// <param name="component">The component context.</param>
21 /// <returns>An entry from the cache, or a new, empty one, if none is found.</returns>
22 public ICache<TKey, TResult> GetCache<TKey, TResult>(Type component) {
23 var cacheKey = new CacheKey(component, typeof(TKey), typeof(TResult));
24 var result = _caches.GetOrAdd(cacheKey, k => new Cache<TKey, TResult>(_cacheContextAccessor));
25 return (Cache<TKey, TResult>)result;
26 }
27 }
这里的要点是缓存介质是一个并发字典,存储内容为Cache类型的对象,它的Key是一个component(使用ICacheManager的当前类型)、Key的类型以及结果类型的一个三元组。
这里的Get方法返回的是一个ICache类型的对象,在CacheManager中是这样调用的:
1 public ICache<TKey, TResult> GetCache<TKey, TResult>() {
2 return _cacheHolder.GetCache<TKey, TResult>(_component);
3 }
4
5 public TResult Get<TKey, TResult>(TKey key, Func<AcquireContext<TKey>, TResult> acquire) {
6 return GetCache<TKey, TResult>().Get(key, acquire);
7 }
以及ICache:
1 public interface ICache<TKey, TResult> {
2 TResult Get(TKey key, Func<AcquireContext<TKey>, TResult> acquire);
3 }
这样就发现最终acquire是在Cache对象中被使用的。
Cache & CacheEntry
1 public class Cache<TKey, TResult> : ICache<TKey, TResult> {
2 private readonly ICacheContextAccessor _cacheContextAccessor;
3 private readonly ConcurrentDictionary<TKey, CacheEntry> _entries;
4
5 public Cache(ICacheContextAccessor cacheContextAccessor) {
6 _cacheContextAccessor = cacheContextAccessor;
7 _entries = new ConcurrentDictionary<TKey, CacheEntry>();
8 }
9
10 public TResult Get(TKey key, Func<AcquireContext<TKey>, TResult> acquire) {
11 var entry = _entries.AddOrUpdate(key,
12 // "Add" lambda
13 k => AddEntry(k, acquire),
14 // "Update" lambda
15 (k, currentEntry) => UpdateEntry(currentEntry, k, acquire));
16
17 return entry.Result;
18 }
19
20 private CacheEntry AddEntry(TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
21 var entry = CreateEntry(k, acquire);
22 PropagateTokens(entry);
23 return entry;
24 }
25
26 private CacheEntry UpdateEntry(CacheEntry currentEntry, TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
27 var entry = (currentEntry.Tokens.Any(t => t != null && !t.IsCurrent)) ? CreateEntry(k, acquire) : currentEntry;
28 PropagateTokens(entry);
29 return entry;
30 }
31
32 private void PropagateTokens(CacheEntry entry) {
33 // Bubble up volatile tokens to parent context
34 if (_cacheContextAccessor.Current != null) {
35 foreach (var token in entry.Tokens)
36 _cacheContextAccessor.Current.Monitor(token);
37 }
38 }
39
40
41 private CacheEntry CreateEntry(TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
42 var entry = new CacheEntry();
43 var context = new AcquireContext<TKey>(k, entry.AddToken);
44
45 IAcquireContext parentContext = null;
46 try {
47 // Push context
48 parentContext = _cacheContextAccessor.Current;
49 _cacheContextAccessor.Current = context;
50
51 entry.Result = acquire(context);
52 }
53 finally {
54 // Pop context
55 _cacheContextAccessor.Current = parentContext;
56 }
57 entry.CompactTokens();
58 return entry;
59 }
60
61 private class CacheEntry {
62 private IList<IVolatileToken> _tokens;
63 public TResult Result { get; set; }
64
65 public IEnumerable<IVolatileToken> Tokens {
66 get {
67 return _tokens ?? Enumerable.Empty<IVolatileToken>();
68 }
69 }
70
71 public void AddToken(IVolatileToken volatileToken) {
72 if (_tokens == null) {
73 _tokens = new List<IVolatileToken>();
74 }
75
76 _tokens.Add(volatileToken);
77 }
78
79 public void CompactTokens() {
80 if (_tokens != null)
81 _tokens = _tokens.Distinct().ToArray();
82 }
83 }
84 }
View Code
从上面代码就可以看出,当实例化一个Cache的时候,_entries字段也是一个空的字典,在这种情况下一定调用AddEntry方法新建一个Entry添加到_entries中:
1 private CacheEntry CreateEntry(TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
2 var entry = new CacheEntry();
3 var context = new AcquireContext<TKey>(k, entry.AddToken);
4
5 IAcquireContext parentContext = null;
6 try {
7 // Push context
8 parentContext = _cacheContextAccessor.Current;
9 _cacheContextAccessor.Current = context;
10
11 entry.Result = acquire(context);
12 }
13 finally {
14 // Pop context
15 _cacheContextAccessor.Current = parentContext;
16 }
17 entry.CompactTokens();
18 return entry;
19 }
entry.Resulte最终是通过acquire加上当前的上下文context创建的(注上面代码中ctx.Monitor这个方法实际上是entry.AddToken),换句话说ctx.Monitor(_clock.When( TimeSpan.FromSeconds(5)));这句代码是将_clock.When( TimeSpan.FromSeconds(5))返回的Token绑定到CacheEntry的_tokens字段上。
当获取一个已经存在的缓存对象时:
1 private CacheEntry UpdateEntry(CacheEntry currentEntry, TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
2 var entry = (currentEntry.Tokens.Any(t => t != null && !t.IsCurrent)) ? CreateEntry(k, acquire) : currentEntry;
3 PropagateTokens(entry);
4 return entry;
5 }
将当前Entry上的所有Token拿出判断,如果都没有过期,那么就返回当前Entry,否则重新创建一个。
以上就是CacheManager的使用方法和它的实现原理,但是上面内容由一个一直没提到的东西ICacheContextAccessor _cacheContextAccessor;
它的默认实现如下,拥有一个线程静态的静态获取上下文属性:
1 public class DefaultCacheContextAccessor : ICacheContextAccessor {
2 [ThreadStatic]
3 private static IAcquireContext _threadInstance;
4
5 public static IAcquireContext ThreadInstance {
6 get { return _threadInstance; }
7 set { _threadInstance = value; }
8 }
9
10 public IAcquireContext Current {
11 get { return ThreadInstance; }
12 set { ThreadInstance = value; }
13 }
14 }
在整个解决方案中搜索ICacheContextAccessor得到下面结果:
主要涉及它的对象有:Cache、DefaultCacheHolder和DefaulteParallelCacheContext。
针对这个问题,鉴于本篇已经较长,将再开一篇来说清楚它的作用。