一步步实现一个基本的缓存模块·续, 添加Memcached调用实现

8/3/2015来源:C#应用人气:1778

一步步实现一个基本的缓存模块·续, 添加Memcached调用实现

jusfr 原创,转载请注明来自博客园。

在之前的实现中,我们初步实现了一个缓存模块:包含一个基于Http请求的缓存实现,一个基于HttPRuntime.Cache进程级的缓存实现,但观察代码,会发现如下问题:

1. 有部分逻辑如 Boolean TryGet<T>(String key, out T entry) 的实现有重复现象,Do not repeat yourself 提醒我们这里可以改进;2. 分区特性虽然实现了,但是使用了额外的接口承载,而大多数运用中,调用者无论是操作缓存项的创建还是过期,都不太关心分区参数 Region;的机制问题,计数和全部过期貌似不太现实,从这个接口派生恐怕不妥,怎么办?3. IHttpRuntimeCacheProvider 接口中功能太多,本文要添加一个基于 Memcached 的缓存实现类,而 Memcached 天然不支持遍历等操作怎么办?

处理第1个问题,先梳理一下缓存获取即 GetOrCreate 逻辑,多数情况是这样的

1)尝试从某容器或客户端如 HttpContext.Current.Items、HttpRuntime.Cache、MemcachedClient 判断缓存是否存在及获取缓存对象;2)缓存对象存在时进行类型对比,比如 id 已经被缓存成整型,现在新接口尝试将 Guid 类型写入,本文使用严格策略,该操作将抛出 InvalidOperationException 异常;3)缓存不存在时,执行委托计算出缓存值,将其写入容器;

可以看出, GetOrCreate 将调用 TryGet 方法及 Overwrite 方法,我们可以使用抽象类,将前者写成具体实现,将后两者写成抽象方法,由具体子类去实现。

 1     public interface ICacheProvider { 2         Boolean TryGet<T>(String key, out T entry); 3         T GetOrCreate<T>(String key, Func<T> function); 4         T GetOrCreate<T>(String key, Func<String, T> factory); 5         void Overwrite<T>(String key, T entry); 6         void Expire(String key); 7     } 8  9     public abstract class CacheProvider : ICacheProvider {10         protected virtual String BuildCacheKey(String key) {11             return key;12         }13         14         protected abstract Boolean InnerTryGet(String key, out Object entry);15 16         public virtual Boolean TryGet<T>(String key, out T entry) {17             String cacheKey = BuildCacheKey(key);18             Object cacheEntry;19             Boolean exist = InnerTryGet(cacheKey, out cacheEntry);20             if (exist) {21                 if (cacheEntry != null) {22                     if (!(cacheEntry is T)) {23                         throw new InvalidOperationException(String.Format("缓存项`[{0}]`类型错误, {1} or {2} ?",24                             key, cacheEntry.GetType().FullName, typeof(T).FullName));25                     }26                     entry = (T)cacheEntry;                    27                 }28                 else {29                     entry = (T)((Object)null);30                 }31             }32             else {33                 entry = default(T);34             }35             return exist;36         }37 38         public virtual T GetOrCreate<T>(String key, Func<T> function) {39             T entry;40             if (TryGet(key, out entry)) {41                 return entry;42             }43             entry = function();44             Overwrite(key, entry);45             return entry;46         }47 48         public virtual T GetOrCreate<T>(String key, Func<String, T> factory) {49             T entry;50             if (TryGet(key, out entry)) {51                 return entry;52             }53             entry = factory(key);54             Overwrite(key, entry);55             return entry;56         }57 58         public abstract void Overwrite<T>(String key, T value);59 60         public abstract void Expire(String key);61     }

抽象类 CacheProvider 的 InnerTryGet、Overwrite、Expire 是需要实现类来完成的,GetOrCreate 调用它们来完成核心逻辑;于是HttpContextCacheProvider 的实现,逻辑在父类实现后,看起来非常简洁了:

 1     public class HttpContextCacheProvider : CacheProvider, ICacheProvider { 2         private const String _prefix = "HttpContextCacheProvider_"; 3         protected override String BuildCacheKey(String key) { 4             return String.Concat(_prefix, key); 5         } 6  7         protected override Boolean InnerTryGet(String key, out Object entry) { 8             Boolean exist = false; 9             entry = null;10             if (HttpContext.Current.Items.Contains(key)) {11                 exist = true;12                 entry = HttpContext.Current.Items[key];13             }14             return exist;15         }16 17         public override void Overwrite<T>(String key, T entry) {18             HttpContext.Current.Items[BuildCacheKey(key)] = entry;19         }20 21         public override void Expire(String key) {22             HttpContext.Current.Items.Remove(BuildCacheKey(key));23         }24     }

这里不准备为基于 HttpContext 的缓存提供太多特性,但基于 HttpRuntime.Cache 的缓存就需要像过期之类的功能,在实现之前先考虑问题2

首先,既然用户没有必要甚至不知道分区存在,我们直接实现支持分区特性的子类好了;然后,计数与过期功能 HttpRuntime.Cache 支持但 Memcached 不,所以这部分功能需要从 IHttpRuntimeCacheProvider 中拆分出来,没错,扩展方法!于是拆分如下:

 1     public interface IRegion { 2         String Region { get; } 3     } 4  5     public interface IHttpRuntimeCacheProvider : ICacheProvider { 6         T GetOrCreate<T>(String key, Func<T> function, TimeSpan slidingExpiration); 7         T GetOrCreate<T>(String key, Func<T> function, DateTime absoluteExpiration); 8         void Overwrite<T>(String key, T value, TimeSpan slidingExpiration); 9         void Overwrite<T>(String key, T value, DateTime absoluteExpiration);        10     }

其中IHttpRuntimeCacheProvider接口定义了带有过期参数的缓存操作方法,我们需要实现抽象方法与额外接口如下:

 1 public class HttpRuntimeCacheProvider : CacheProvider, IHttpRuntimeCacheProvider, IRegion { 2         private static readonly Object _nullEntry = new Object(); 3         private String _prefix = "HttpRuntimeCacheProvider_"; 4  5         public virtual String Region { get; private set; } 6  7         public HttpRuntimeCacheProvider() { 8         } 9 10         public HttpRuntimeCacheProvider(String region) {11             Region = region;12         }13 14         protected override bool InnerTryGet(String key, out object entry) {15             entry = HttpRuntime.Cache.Get(key);16             return entry != null;17         }18 19         protected override String BuildCacheKey(String key) {20             //Region 为空将被当作  String.Empty 处理21             return Region == null22                 ? String.Concat(_prefix, key)23                 : String.Concat(_prefix, Region, key);24         }25 26         private Object BuildCacheEntry<T>(T value) {27             Object entry = value;28             if (value == null) {29                 entry = _nullEntry;30             }31             return entry;32         }33 34 35         public T GetOrCreate<T>(String key, Func<T> function, TimeSpan slidingExpiration) {36             T value;37             if (TryGet<T>(key, out value)) {38                 return value;39             }40             value = function();41             Overwrite(key, value, slidingExpiration);42             return value;43         }44 45         public T GetOrCreate<T>(String key, Func<T> function, DateTime absoluteExpiration) {46             T value;47             if (TryGet<T>(key, out value)) {48                 return value;49             }50             value = function();51             Overwrite(key, value, absoluteExpiration);52             return value;53         }54 55         public override void Overwrite<T>(String key, T value) {56             HttpRuntime.Cache.Insert(BuildCacheKey(key), BuildCacheEntry<T>(value));57         }58 59         //slidingExpiration 时间内无访问则过期60         public void Overwrite<T>(String key, T value, TimeSpan slidingExpiration) {61             HttpRuntime.Cache.Insert(BuildCacheKey(key), BuildCacheEntry<T>(value), null,62                 Cache.NoAbsoluteExpiration, slidingExpiration);63         }64 65         //absoluteExpiration 时过期66         public void Overwrite<T>(String key, T value, DateTime absoluteExpiration) {67             HttpRuntime.Cache.Insert(BuildCacheKey(key), BuildCacheEntry<T>(value), null,68                 absoluteExpiration, Cache.NoSlidingExpiration);69         }70 71         public override void Expire(String key) {72             HttpRuntime.Cache.Remove(BuildCacheKey(key));73         }74 75         internal Boolean Hit(DictionaryEntry entry) {76             return (entry.Key is String)77                 && ((String)entry.Key).StartsWith(BuildCacheKey(String.Empty));78         }79     }

HttpRuntimeCacheProvider 暴露了一个 internal 修饰的方法,提供给扩展方法调用:

 1     public static