我有话说

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

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

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

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

缓存决策

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

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

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

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

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

如何适合缓存决策

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

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

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

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

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

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

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

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

 
如何管理这些判断规则?

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

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

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

     public static partial class EqualsMonitorManager<TKey, TValue>
where TValue : IEquatable<TValue>
{
private static class MonitorCaller<TCallerKey>
{
public static Action<TCallerKey, TValue> Add; public static Action<TCallerKey> Remove; public static Func<TCallerKey, Func<TValue, bool>, TValue> Get; public static Func<TCallerKey, TValue, bool> IsMonitoring;
} #region Members private static Dictionary<string, List<TValue>> _dicStringMonitor = new Dictionary<string, List<TValue>>(); #endregion static EqualsMonitorManager()
{
StringMonitorCallerInit();
} private static void StringMonitorCallerInit()
{
MonitorCaller<string>.Add = (string key, TValue value) =>
{
if (!_dicStringMonitor.ContainsKey(key))
{
_dicStringMonitor.Add(key, new List<TValue>());
} _dicStringMonitor[key].Add(value);
}; MonitorCaller<string>.Remove = (string key) =>
{
if (_dicStringMonitor.ContainsKey(key))
_dicStringMonitor.Remove(key);
}; MonitorCaller<string>.Get = (string key, Func<TValue, bool> predicate) =>
{
if (_dicStringMonitor.ContainsKey(key))
return _dicStringMonitor[key].FirstOrDefault(predicate);
else
return default(TValue);
}; MonitorCaller<string>.IsMonitoring = (string key, TValue value) =>
{
if (!_dicStringMonitor.ContainsKey(key))
{
return false;
} return _dicStringMonitor[key].Exists(x => x.Equals(value));
};
}
}
     public static partial class EqualsMonitorManager<TKey, TValue>
{
public static void Add(TKey key, TValue value)
{
if (key == null)
{
throw new ArgumentNullException();
}
MonitorCaller<TKey>.Add(key, value);
} public static void Remove(TKey key)
{
if (key == null)
{
throw new ArgumentNullException();
}
MonitorCaller<TKey>.Remove(key);
} public static TValue Get(TKey key, Func<TValue, bool> predicate)
{
if (key == null)
{
throw new ArgumentNullException();
}
return MonitorCaller<TKey>.Get(key, predicate);
} public static bool IsMonitoring(TKey key, TValue value)
{
if (key == null)
{
throw new ArgumentNullException();
} return MonitorCaller<TKey>.IsMonitoring(key, value);
}
}

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

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

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

 
初始化判断规则

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

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

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

     public class RedisCacheMonitor : IEquatable<RedisCacheMonitor>
{
public string Key { get { return MonitorConstant.REDIS_KEY; } } public string TableName { get; set; } public string[] Fields { get; set; } #region IEquatable<RedisCacheMonitor> 成员 public bool Equals(RedisCacheMonitor other)
{
if (other == null)
{
return false;
} return this.TableName == other.TableName;
} #endregion
}

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

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

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

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

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

自动缓存决策与手动缓存决策

 
为什么会有自动和手动两种?

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

 
如何实现手动缓存决策?
 var monitor = EqualsMonitorManager<string, RedisCacheMonitor>.Get(MonitorConstant.REDIS_KEY, x => x.TableName == tableName);

 if (monitor != null)
{
//todo something
}

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

 
如何实现自动缓存决策?

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

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

大概思路是这样的:

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

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

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

     public class DataWrapper<T> : EFWrapperBase<T>
where T : class,new()
{
public DataWrapper()
{
base.Context.EventRegistModel += ModelRegister.Regist;
} public override async Task<int> SaveAsync()
{
var result = await base.SaveAsync(); SaveToRedis(); return result;
} private void SaveToRedis()
{
try
{
var type = typeof(T);
var monitor = EqualsMonitorManager<string, RedisCacheMonitor>.Get(MonitorConstant.REDIS_KEY, x => x.TableName == type.Name);
if (monitor != null)
{
foreach (var entity in base.DbSet.Local)
{
foreach (var field in monitor.Fields)
{
var pi = type.GetProperty(field);
RedisSingleton.GetInstance.Client.HSet(type.Name, string.Format("{0}:{1}", pi.Name, pi.GetValue(entity, null).ToString()), entity);
}
}
}
}
catch (Exception ex)
{
Logger.Error(ex.ToString());
}
} }

源码

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

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

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

  1. 浏览器 HTTP 协议缓存机制详解--网络缓存决策机制流程图

    1.缓存的分类 2.浏览器缓存机制详解 2.1 HTML Meta标签控制缓存 2.2 HTTP头信息控制缓存 2.2.1 浏览器请求流程 2.2.2 几个重要概念解释 3.用户行为与缓存 4.Ref ...

  2. C# - 缓存OutputCache(二)缓存详细介绍

    本文是通过网上&个人总结的 1.缓存介绍 缓存是为了提高访问速度,而做的技术. 缓存主要有以下几类:1)客户端缓存Client Caching 2)代理缓存Proxy Caching 3)方向 ...

  3. Spring自定义缓存管理及配置Ehcache缓存

    spring自带缓存.自建缓存管理器等都可解决项目部分性能问题.结合Ehcache后性能更优,使用也比较简单. 在进行Ehcache学习之前,最好对Spring自带的缓存管理有一个总体的认识. 这篇文 ...

  4. [转]MVC3缓存之一:使用页面缓存

    本文转自:http://www.cnblogs.com/parry/archive/2011/03/19/OutputCache_In_MVC3.html 在以前的WebForm的开发中,在页面的头部 ...

  5. 使用Retrofit和Okhttp实现网络缓存。无网读缓存,有网根据过期时间重新请求 (转)

    使用Retrofit和Okhttp实现网络缓存,更新于2016.02.02原文链接:http://www.jianshu.com/p/9c3b4ea108a7 本文使用 Retrofit2.0.0-b ...

  6. MVC3缓存之一:使用页面缓存

    MVC3缓存之一:使用页面缓存 在MVC3中要如果要启用页面缓存,在页面对应的Action前面加上一个OutputCache属性即可. 我们建一个Demo来测试一下,在此Demo中,在View的Hom ...

  7. MVC缓存OutPutCache学习笔记 (二) 缓存及时化VaryByCustom

    <MVC缓存OutPutCache学习笔记 (一) 参数配置> 本篇来介绍如何使用 VaryByCustom参数来实现缓存的及时化.. 根据数据改变来及时使客户端缓存过期并更新.. 首先更 ...

  8. MVC缓存02,使用数据层缓存,添加或修改时让缓存失效

    在"MVC缓存01,使用控制器缓存或数据层缓存"中,在数据层中可以设置缓存的有效时间.但这个还不够"智能",常常希望在编辑或创建的时候使缓存失效,加载新的数据. ...

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

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

随机推荐

  1. 铁乐学python_day02-作业

    1.判断下列逻辑语句的True,False. 1)1 > 1 or 3 < 4 or 4 > 5 and 2 > 1 and 9 > 8 or 7 < 6 解题思路 ...

  2. Hadoop HBase概念学习系列之优秀行键设计(十六)

    我们通过行键访问HBase.尽管使用扫描过滤器可以一次性指明大量的键,但是HBase仅仅能够根据行键识别出一行. 优秀的行键设计可以保证良好的HBase性能. 1.行键存在于HBase中的每一个单元格 ...

  3. python3: 字符串和文本

    1. 分割字符串-使用多个界定符[re.split()] >>> line = 'asdf fjdk; afed, fjek,asdf, foo' >>> impo ...

  4. RTCM32编解码中的一些概念及相关文献阅读

    1. IODC和 IODE ——  导航电文相关.iode/iodc是在GPS系统的ICD2中定义的参数,iode指星历数据事件,iodc指星钟数据事件. IOD 是 issue of data ,数 ...

  5. Linux--安全加固01

    Redhat是目前企业中用的最多的一类Linux,而目前针对Redhat攻击的黑客也越来越多了.我们要如何为这类服务器做好安全加固工作呢? 一. 账户安全 1.1 锁定系统中多余的自建帐号 检查方法: ...

  6. [CEOI2017]Building Bridges

    题目 斜率优化思博题,不想写了 之后就一直\(95\)了,于是靠肮脏的打表 就是更新了一下凸壳上二分斜率的写法,非常清爽好写 就当是挂个板子了 #include<algorithm> #i ...

  7. 【转】 iOS播放视频时候,忽略设备静音按钮

    用户有时会在静音模式下观看视频,如果不主动设置的话,视频是没有声音的,通过AVAudioSession可开启以视频为主导的播放模式, 首先需要导入,AVFoundtion.framework,在控制播 ...

  8. Python自动化之logging模块

    Logging模块构成 主要分为四个部分: Loggers:提供应用程序直接使用的接口 Handlers:将Loggers产生的日志传到指定位置 Filters:对输出日志进行过滤 Formatter ...

  9. python logging模块日志回滚TimedRotatingFileHandler

    # coding=utf-8 import logging import time import os import logging.handlers import re def logger(app ...

  10. Kafka设计解析(十四)Kafka producer介绍

    转载自 huxihx,原文链接 Kafka producer介绍 Kafka 0.9版本正式使用Java版本的producer替换了原Scala版本的producer.本文着重讨论新版本produce ...