【PaPaPa】实现缓存决策

8/10/2015来源:ASP.NET技巧人气:1741

【PaPaPa】实现缓存决策 - 让你的缓存变的有智慧

我有话说

本来这一篇我打算放到后面再说,可是之前泄漏了一点关于缓存决策的代码后被好多人催更了。

在此感谢大家的支持,让我更有动力的写这个系列。你们的关注让我觉得我的决定是对的,我会坚持下去把这个项目做完。

另外非常感谢老虎,在百忙之中给我们赶出需求文档,当我们在享受周末的时候他还在公司加班,即便这样,他依然为我们的开源项目奉献着。

此时我不知道该说些什么,只能以我的行动来回报大家,废话不多说了,入正题。

缓存决策

先澄清下,这个名字是我杜撰的,因为我觉得在我的项目中它起到了这样的作用。

缓存:在我做的这个功能中涉及到内存和redis两部分的缓存。

决策:我从百度找的翻译,指做出决定或选择,是一种“在各种替代方案中考虑各项因素作出选择”的认知、思考过程。

那么缓存决策到底是干什么的?

说白了就是选择使用数据库还是缓存。

如何适合缓存决策

缓存决策的由来 - 我是懒人

因为我懒,所以我要想办法偷懒。

我希望有一个类库可以帮助我来判断当前的数据是到缓存里取,还是数据库里取。

而为了实现这样的一个功能,我觉得我应该建立一个规则,这个规则来帮助我判断当前数据在缓存里是不是有一份拷贝。

我对缓存的判断规则有什么要求?

就以目前项目来说,我缓存是整表缓存的,所以我需要判断的是当前数据是属于哪个表。

既然如此,那我判断的依据应该是这样:缓存决策规则.表名列表.包含(数据.表名) == true

只要满足上面的条件,说明当前数据在缓存里是有拷贝的。

如何管理这些判断规则?

继续上面提到的包含,我们再分析一下,包含的判断依据其实是逐一比对相等,所以我想了个类名:EqualsMonitorManager,这里的Monitor是监视器的意思,后面的类都会跟这个词有关。

这个类有4个基本的方法:Add、Remove、Get、IsMonitoring,看起来其实是很像字典的对吧?其实内部实现确实依赖了字典,对字典做了一些封装。

为了方面以后扩展支持到更多场景而不局限于缓存,我定义的时候使用到了泛型。

 1     public static partial class EqualsMonitorManager<TKey, TValue> 2         where TValue : IEquatable<TValue> 3     { 4         PRivate static class MonitorCaller<TCallerKey> 5         { 6             public static Action<TCallerKey, TValue> Add; 7  8             public static Action<TCallerKey> Remove; 9 10             public static Func<TCallerKey, Func<TValue, bool>, TValue> Get;11 12             public static Func<TCallerKey, TValue, bool> IsMonitoring;13         }14 15         #region Members16 17         private static Dictionary<string, List<TValue>> _dicStringMonitor = new Dictionary<string, List<TValue>>();18 19         #endregion20 21         static EqualsMonitorManager()22         {23             StringMonitorCallerInit();24         }25 26         private static void StringMonitorCallerInit()27         {28             MonitorCaller<string>.Add = (string key, TValue value) =>29             {30                 if (!_dicStringMonitor.ContainsKey(key))31                 {32                     _dicStringMonitor.Add(key, new List<TValue>());33                 }34 35                 _dicStringMonitor[key].Add(value);36             };37 38             MonitorCaller<string>.Remove = (string key) =>39             {40                 if (_dicStringMonitor.ContainsKey(key))41                     _dicStringMonitor.Remove(key);42             };43 44             MonitorCaller<string>.Get = (string key, Func<TValue, bool> predicate) =>45             {46                 if (_dicStringMonitor.ContainsKey(key))47                     return _dicStringMonitor[key].FirstOrDefault(predicate);48                 else49                     return default(TValue);50             };51 52             MonitorCaller<string>.IsMonitoring = (string key, TValue value) =>53             {54                 if (!_dicStringMonitor.ContainsKey(key))55                 {56                     return false;57                 }58 59                 return _dicStringMonitor[key].Exists(x => x.Equals(value));60             };61         }62     }
View Code

 1     public static partial class EqualsMonitorManager<TKey, TValue> 2     { 3         public static void Add(TKey key, TValue value) 4         { 5             if (key == null) 6             { 7                 throw new ArgumentNullException(); 8             } 9             MonitorCaller<TKey>.Add(key, value);10         }11 12         public static void Remove(TKey key)13         {14             if (key == null)15             {16                 throw new ArgumentNullException();17             }18             MonitorCaller<TKey>.Remove(key);19         }20 21         public static TValue Get(TKey key, Func<TValue, bool> predicate)22         {23             if (key == null)24             {25                 throw new ArgumentNullException();26             }27             return MonitorCaller<TKey>.Get(key, predicate);28         }29 30         public static bool IsMonitoring(TKey key, TValue value)31         {32             if (key == null)33             {34                 throw new ArgumentNullException();35             }36 37             return MonitorCaller<TKey>.IsMonitoring(key, value);38         }39     }
View Code

这里的代码用到了老赵博客中的一篇关于“逆泛型”的代码,这里是未经优化的,写的仓促。

这里我就不多解释为什么会这么写这个类了,有兴趣可以去翻老赵的博客,写的很详细,对于初学者来说这里有点绕,建议可以去看看。

这里只是创建了一个最基础的封装过的“字典”,用于管理判断规则。

初始化判断规则

有了管理规则的类,那么我们的项目中首先要做的就是初始化这些规则,否则没有规则后面的写下去也用不了。

细心的朋友可能会发现,EqualsMonitorManager的TValue需要继承自IEquatable接口,因为内部判断相等是用了这个接口的Equals方法。

那么,我们第一个缓存决策类出现了,它就是RedisCacheMonitor。

 1     public class RedisCacheMonitor : IEquatable<RedisCacheMonitor> 2     { 3         public string Key { get { return MonitorConstant.REDIS_KEY; } } 4  5         public string TableName { get; set; } 6  7         public string[] Fields { get; set; } 8  9         #region IEquatable<RedisCacheMonitor> 成员10 11         public bool Equals(RedisCacheMonitor other)12         {13             if (other == null)14             {15                 return false;16             }17 18             return this.TableName == other.TableName;19         }20 21         #endregion22     }
View Code

我们可以发现,这个类的自由度很大,唯一的约束就是要实现IEquatable接口,这样EqualMonitorManager的可扩展性就充分被利用了起来。而RedisCacheMonitor就可以任由我们来发挥,我们只需要告诉EqualMonitorManager如何去判断相等即可。

TableName表示缓存的表名,Fields是使用了RedisHGet命令的一个参数名,表示哪些字段可以作为关键字来查询数据或者说需要缓存哪些字段为关键字。

接下来就是如何把一个RedisCacheMonitor加入到EqualMonitorManager

1 var monitor = new RedisCacheMonitor() { TableName = "User", Fields = new string[] { "Id", "UserName" } };2 EqualsMonitorManager<string, RedisCacheMonitor>.Add(monitor.Key, monitor);

是的,就这么简单,我们的缓存规则就加完了。剩下就是操作Redis,把User表缓存起来我就不