zoukankan      html  css  js  c++  java
  • Orchard详解--第五篇 CacheManager

      上一篇文章介绍了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。

    针对这个问题,鉴于本篇已经较长,将再开一篇来说清楚它的作用。

  • 相关阅读:
    javascript 数字时钟
    ubuntu下键盘背景灯光设置
    使用百度地图SDK
    ListView 的Item状态改变和保存
    继续Jsoup 正方教务系统的教学质量评价一键好评
    Java下的可视化开发工具使用 WindowBuilder Pro
    js 数组排序
    js数组去重
    js call() apply()
    [总结] js的2种继承方式详解
  • 原文地址:https://www.cnblogs.com/selimsong/p/6010963.html
Copyright © 2011-2022 走看看