【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 Members
16
17         private static Dictionary<string, List<TValue>> _dicStringMonitor = new Dictionary<string, List<TValue>>();
18
19         #endregion
20
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                 else
49                     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     }

 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     }

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

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

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

  初始化判断规则

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

细心的朋友可能会发现,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         #endregion
22     }

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

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

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

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

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

自动缓存决策与手动缓存决策
  为什么会有自动和手动两种?

因为我操作数据库用的EF,查询条件是表达式树,为了降低解析表达式树的工作量暂时选择了自动和手动。

  如何实现手动缓存决策?
1 var monitor = EqualsMonitorManager<string, RedisCacheMonitor>.Get(MonitorConstant.REDIS_KEY, x => x.TableName == tableName);
2
3 if (monitor != null)
4 {
5     //todo something
6 }

手动决策很简单,只要尝试获取一下即可,获取到monitor就说明被缓存了,下面就可以直接取缓存了。

  如何实现自动缓存决策?

看到第一篇的应该对下面的代码有印象,我把之前写的内容直接copy过来一份:

SaveChangesAsync是EF的异步保存方法,我们要做的事情其实很简单,就是拦截保存方法,代码中是SaveAsync,这个是我们自己针对EF封装后的方法。

大概思路是这样的:

想要让 SaveAsync 听我们的话, override 就派上了用场,重写 SaveAsync。

调用基类的 SaveAsync 后,再加上保存到Redis的代码。

这样一个SaveAsync就变成了做2件事,先保存到数据库再保存到Redis,从而杜绝了代码中到处写保存到Redis的重复代码。

 1     public class DataWrapper<T> : EFWrapperBase<T>
 2         where T : class,new()
 3     {
 4         public DataWrapper()
 5         {
 6             base.Context.EventRegistModel += ModelRegister.Regist;
 7         }
 8
 9         public override async Task<int> SaveAsync()
10         {
11             var result = await base.SaveAsync();
12
13             SaveToRedis();
14
15             return result;
16         }
17
18         private void SaveToRedis()
19         {
20             try
21             {
22                 var type = typeof(T);
23                 var monitor = EqualsMonitorManager<string, RedisCacheMonitor>.Get(MonitorConstant.REDIS_KEY, x => x.TableName == type.Name);
24                 if (monitor != null)
25                 {
26                     foreach (var entity in base.DbSet.Local)
27                     {
28                         foreach (var field in monitor.Fields)
29                         {
30                             var pi = type.GetProperty(field);
31                             RedisSingleton.GetInstance.Client.HSet(type.Name, string.Format("{0}:{1}", pi.Name, pi.GetValue(entity, null).ToString()), entity);
32                         }
33                     }
34                 }
35             }
36             catch (Exception ex)
37             {
38                 Logger.Error(ex.ToString());
39             }
40         }
41
42     }

源码

源码地址:http://git.oschina.net/doddgu/PaPaPa

PS:其实想想真的不难,主要是一种思路,用到的都是基本的C#语法,关键在于你敢不敢想,而我敢想了,你还在犹豫吗?后面我们会有更多敢想敢做的事,欢迎你的加入。

今天公司年会,白天碰不到电脑了,晚上回来统一回复

先睡了,有点困了 ^_^

时间: 2024-08-02 19:16:26

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

[原创]java WEB学习笔记93:Hibernate学习之路---Hibernate 缓存介绍,缓存级别,使用二级缓存的情况,二级缓存的架构集合缓存,二级缓存的并发策略,实现步骤,集合缓存,查询缓存,时间戳缓存

本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 -----------------------------------------------------------------------------------------------------------------

Java缓存学习之三:CDN缓存机制

CDN是什么? 关于CDN是什么,此前网友详细介绍过. CDN是Content Delivery Network的简称,即"内容分发网络"的意思.一般我们所说的CDN加速,一般是指网站加速或者用户下载资源加速. 举个通俗的例子: 谈到CDN的作用,可以用8年买火车票的经历来形象比喻:8年前,还没有火车票代售点一说,12306.cn更是无从说起.那时候火车票还只能在火车站的售票大厅购买,而我所住的小县城并不通火车,火车票都要去市里的火车站购买,而从县城到市里,来回就是4个小时车程,简直就

TimesTen 应用层数据库缓存学习:12. 管理缓存环境

缓存和复制代理的启停和状态查看 cache agent的作用是将监控Oracle中数据的变化,并更新到TimesTen.因此,对于只读和AWT缓存组,cache agent都是必需的. cache agent的启停 ttisql> call ttcachestart ttisql> call ttcachestop 或者 $ ttadmin -cachestart DSN $ ttadmin -cachestop DSN replication agent的启停 ttisql> call

缓存学习中未命中的缓存情况的处理

this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); cache中还是要设置空对象来处null的,提高没有缓存对象的,缓存请求的命中率,防止缓存击穿. 缓存重要点: 1.命中率. 2.缓存数据大小. 3.缓存的失效情况. 4.缓存的过期设置. 5.缓存的数据,跨系统(win,linux),网络传输量.

php 缓存工具类 实现网页缓存

php程序在抵抗大流量访问的时候动态网站往往都是难以招架,所以要引入缓存机制,一般情况下有两种类型缓存 一.文件缓存 二.数据查询结果缓存,使用内存来实现高速缓存 本例主要使用文件缓存. 主要原理使用缓存函数来存储网页显示结果,如果在规定时间里再次调用则可以加载缓存文件. 工具类代码: // 文件缓存类 class Cache { /** * $dir : 缓存文件存放目录 * $lifetime : 缓存文件有效期,单位为秒 * $cacheid : 缓存文件路径,包含文件名 * $ext :

linux缓存系统学习之浏览器缓存

最近发现自己学习的东西太杂,不成系统,所以准备整理后再出发.整理也是一种升华.在学习的路上多总结,感觉很好! 这里从缓存开始说起,好久都没有写什么博客了,内容有不对的地方欢迎指正 好像大多问题都能通过加缓存解决,什么叫缓存呢,缓存就是把需要花费昂贵开销的计算结果保存起来,在之后访问直接取出,这个昂贵的开销可以是昂贵的计算,也可以是昂贵的带宽费用等等 从client端出发,首先来说说浏览器缓存,也许从某种角度上看,浏览器也是一个web服务器,也能算一级缓存,内部也存在的各种缓存协商的过程 last

全面剖析Smarty缓存机制一[三种缓存方式]

今天主要全面总结下Smarty模板引擎中强大的缓存机制,缓存机制有效减少了系统对服务器的压力,而这也是很多开发者喜欢Smarty的原因之一,由于篇幅较大,便于博友阅读,这篇文章将剖析Smarty缓存的几种方式,下篇文章着重讲解下设置缓存及清除缓存的技巧方法(其中包含缓存集合方法). 一.Smarty缓存的几种方式缓存机制中,分为全局缓存.部分缓存.局部缓存三种方式,后面会一一讲述,下面是缓存设置前,Smarty类方法基本目录设置如下:$smarty->Smarty();$smarty->tem

浅析ThinkPHP缓存之快速缓存(F方法)和动态缓存(S方法)

系统默认的缓存方式是采用File方式缓存,我们可以在项目配置文件里面定义其他的缓存方式,例如,修改默认的缓存方式为Xcache(当然,你的环境需要支持Xcache) 对于File方式缓存下的缓存目录下面因为缓存数据过多而导致存在大量的文件问题,ThinkPHP也给出了解决方案,可以启用哈希子目录缓存的方式. 'DATA_CACHE_SUBDIR'=>true 还可以设置哈希目录的层次,例如 'DATA_PATH_LEVEL'=>2 就可以根据缓存标识的哈希自动创建多层子目录来缓存. S方法支持

mybatis学习笔记(14)-查询缓存之中的一个级缓存

mybatis学习笔记(14)-查询缓存之中的一个级缓存 mybatis学习笔记14-查询缓存之中的一个级缓存 查询缓存 一级缓存 一级缓存工作原理 一级缓存測试 一级缓存应用 本文主要讲mybatis的一级缓存.一级缓存是SqlSession级别的缓存. 查询缓存 mybatis提供查询缓存.用于减轻数据压力,提高数据库性能. mybaits提供一级缓存,和二级缓存. 一级缓存是SqlSession级别的缓存.在操作数据库时须要构造sqlSession对象,在对象中有一个数据结构(HashMa