上一篇文章介绍了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 }
上面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 }
从上面代码就可以看出,当实例化一个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。
针对这个问题,鉴于本篇已经较长,将再开一篇来说清楚它的作用。