一种小型后台管理系统通用开发框架中的Cache缓存设计
本片博客记录一下我在实习的公司的后台管理系统开发框架中学习到的一种关于网站的缓存(cache)的实现方法,我会在弄懂的基础上,将该方法在.net core上进行实现。因为公司开发都是基于.net framework的,但是在.net 这一块,.net framework正在逐渐被.net core所取代,而目前公司的前辈们由于开发任务较重,并没有着手使用.net core的打算,所以,我自己打算为公司搭建一个基于.net core的后台开发框架,这对自己是一个挑战,但收获还是很大的,在这个过程中,我学到了很多。下面我记录一下我们公司关于网站设计中cache的一种设计与实现方法(先说在.net mvc下的实现方法,后续会写另一篇.net core的实现方法):
-
总体设计:
我们知道的缓存一般分为3种,分别是 cookies,session和cache,这三种的区别和使用场景我在这里就不说了,网上有大神的博客说的很清楚。cookies主要用于客户端,session和cache用于服务端,本篇主要讲cahe的使用。cache存储于服务器的内存中,允许自定义如何缓存数据项,以及缓存时间有多长。当系统内存缺乏时,缓存会自动移除很少使用或者使用优先级较低的缓存项以释放内存。cache的使用可以提高整个系统的运行效率。
cache在使用上也是(key,value)形式的,关于插入、获取、移除什么的,都可以在cache类中去查看,这里贴出cache这个类的内容:
#region 程序集 system.web, version=4.0.0.0, culture=neutral, publickeytoken=b03f5f7f11d50a3a // c:\program files (x86)\reference assemblies\microsoft\framework\.netframework\v4.5\system.web.dll #endregion using system.collections; using system.reflection; namespace system.web.caching { // // 摘要: // implements the cache for a web application. this class cannot be inherited. [defaultmember("item")] public sealed class cache : ienumerable { public static readonly datetime noabsoluteexpiration; public static readonly timespan noslidingexpiration; public cache(); public object this[string key] { get; set; } public int count { get; } public long effectiveprivatebyteslimit { get; } public long effectivepercentagephysicalmemorylimit { get; } public object add(string key, object value, cachedependency dependencies, datetime absoluteexpiration, timespan slidingexpiration, cacheitempriority priority, cacheitemremovedcallback onremovecallback); public object get(string key); public idictionaryenumerator getenumerator(); public void insert(string key, object value); public void insert(string key, object value, cachedependency dependencies); public void insert(string key, object value, cachedependency dependencies, datetime absoluteexpiration, timespan slidingexpiration, cacheitempriority priority, cacheitemremovedcallback onremovecallback); public object remove(string key); } }
可以看到里面有add,get,insert之类的东西,这些用法网上也有很清楚的讲述,我也不赘述,也说不清楚,哈哈。
下面,结合上面那张示意图,来说明一下我要讲的缓存设计,个人感觉还是比较好的。
<key,value>的形式,就是一个value对应一个key,通过key可以设置value的值,也可以获取value的值。在这里我们把 每个用户登录时生成的一个唯一的 id 做为 cache的key,然后把希望放到缓存中的数据作为value,进行缓存数据的处理。但是,我们放到value中的值,可能是有不同用途不同种类的一些值,比如,登录用户的基本信息,该系统存储的菜单数据,这两个就是用途完全不相干的两类数据,怎么存储呢?再另外使用一个key值不同的cache,这应该也是可以的。但是,我们这里讲的方法只使用一个cache。
具体做法呢,就是把这个value定义为一个 dictionary<key,value>类型的值,这样在value里面,我们就可以通过设置不同的key值,来存储不同用途的缓存数据了。
-
第一步
首先,先定义一个存储value数据的类,代码如下:
usercache.cs
using system.collections.generic; namespace common { public class usercache { private readonly dictionary<string, object> cachedictionary = new dictionary<string, object>(); private readonly object lockobj = new object(); /// <summary> /// 索引器 /// </summary> /// <param name="key">key</param> /// <returns>缓存对象</returns> public object this[string key] { get { lock (lockobj) { return cachedictionary.containskey(key) ? cachedictionary[key] : null; } } set { lock(lockobj) { if (cachedictionary.containskey(key)) { cachedictionary[key] = value; } else { cachedictionary.add(key, value); } } } } public void remove(string key) { lock (lockobj) { if(cachedictionary.containskey(key)) { cachedictionary.remove(key); } } } public void clear() { lock(lockobj) { cachedictionary.clear(); } } } }
上面的代码,用到了一个索引器,这使得我们可以像数组那样用 xxx[index]这样的方法访问和设置数据,从代码中我们可以看到,这个类最终都实现对 cachedictionary 这个字典的操作,因为我们的数据都存储在这个字典中。不管你想存储什么数据只需要定义一个key值,然后存储到字典中即可。
-
第二步
定义好usercache.cs后,我们再来写关于缓存操作的类:
先定义缓存操作类,然后书写关于缓存操作的代码:
webcache.cs(部分)
using system; using system.web; using system.web.caching; using common; namespace console { /// <summary> /// 缓存操作类 /// </summary> public class webcache { #region 私有变量 private const string useridentifykey = "cacheuseridentifykey"; #endregion #region 私有方法 private static string getuseridentify() { if (httpcontext.current.session[useridentifykey] != null) return httpcontext.current.session[useridentifykey].tostring(); var identify = guid.newguid().tostring(); httpcontext.current.session[useridentifykey] = identify; return identify; } private static usercache getusercache() { var identify = getuseridentify(); if (httpcontext.current.cache.get(identify) == null) { httpcontext.current.cache.insert(identify, new usercache(), null, cache.noabsoluteexpiration, new timespan(0, 20, 0), cacheitempriority.high, cacheremovedcallback); } return httpcontext.current.cache.get(identify) as usercache; } /// <summary> /// 缓存被移除时触发 /// </summary> /// <param name="key">被移除的缓存的key</param> /// <param name="value">被移除的缓存的值</param> /// <param name="reason">移除原因</param> private static void cacheremovedcallback(string key, object value, cacheitemremovedreason reason) { // 缓存被移除时执行的操作 // 如果是手动移除,则不处理 //if (reason == cacheitemremovedreason.removed) // return; // 此处访问页面会报错,暂时注释掉 // shownotification(messagetype.warning, "警告", "由于您太久没操作页面已过期,请重新登录!", true); } #endregion } }
首先看上面的代码:
上面三段代码中,核心的代码是第二段,需要注意的是,都是静态方法:
getusercache() 方法
当然,我们还是从第一段代码开始说起,
getuseridentify()
private static string getuseridentify() { if (httpcontext.current.session[useridentifykey] != null) return httpcontext.current.session[useridentifykey].tostring(); var identify = guid.newguid().tostring(); httpcontext.current.session[useridentifykey] = identify; return identify; }
在一个用户登录之初,我们首先给这个用户生成一个唯一的用户 id ,然后把这个id保存到 session中 (session也是<key,value>形式的)。在第二段代码中,通过 getuseridentify()方法获取用户的唯一 id,然后把这个唯一 id作为 cache的key值。
然后,我们来看第二段代码:
getusercache():
private static usercache getusercache() { var identify = getuseridentify(); if (httpcontext.current.cache.get(identify) == null) { httpcontext.current.cache.insert(identify, new usercache(), null, cache.noabsoluteexpiration, new timespan(0, 20, 0), cacheitempriority.high, cacheremovedcallback); } return httpcontext.current.cache.get(identify) as usercache; }
这段代码,首先判断cache中是否有值(是否存在这个key的cache),若不存在,则创建一个(代码中的 new usercache())。.net framework中cache操作使用 httpcontext.current.cache,insert后有若干个参数,意思分别是:
identify:key值;
new usercache():value值;
第三个参数是:缓存依赖项 cachedependency ,这里是 null;
cache.noabsoluteexpiration:绝对过期时间 ,这里设置为无绝对过期时间;
new timespan(0, 20, 0):这是滑动过期时间,此处设置为 20 minite;
cacheitempriority.high:缓存优先级,此处为 high;
cacheremovedcallback: 缓存移除时的回调函数,这个回调函数的参数是固定写法,必须按照规定写,三个参数以及参数类型 不可缺少也不可写错,否则会报错;(具体可见上面的第三段代码)
上面说到,若不存在,则创建一个 ,若存在,那么就直接返回即可。
接下来,在webcache.cs中定义一些公共方法,用来供外界的方法调用,以实现对缓存的操作,代码如下:
webcache.cs(全):
using system; using system.web; using system.web.caching; using common; namespace console { /// <summary> /// 缓存操作类 /// </summary> public class webcache { #region 私有变量 private const string useridentifykey = "cacheuseridentifykey"; #endregion #region 公共方法 /// <summary> /// 获取缓存 /// </summary> /// <param name="key">键</param> /// <returns></returns> public static object getcache(string key) { return getusercache()[key]; } /// <summary> /// 设置缓存 /// </summary> /// <param name="key">键</param> /// <param name="value">值</param> /// <returns></returns> public static bool setcache(string key, object value) { try { var usercache = getusercache(); usercache[key] = value; return true; } catch { return false; } } /// <summary> /// 清空缓存 /// </summary> /// <returns></returns> public static bool clearcache() { try { // 只清除缓存内容 // getusercache().clear(); // 直接从cache里移除 var identify = getuseridentify(); httpcontext.current.cache.remove(identify); return true; } catch { return false; } } /// <summary> /// 移除缓存 /// </summary> /// <param name="key">键</param> /// <returns></returns> public static bool removecache(string key) { try { getusercache().remove(key); return true; } catch { return false; } } #endregion #region 私有方法 private static string getuseridentify() { if (httpcontext.current.session[useridentifykey] != null) return httpcontext.current.session[useridentifykey].tostring(); var identify = guid.newguid().tostring(); httpcontext.current.session[useridentifykey] = identify; return identify; } private static usercache getusercache() { var identify = getuseridentify(); if (httpcontext.current.cache.get(identify) == null) { httpcontext.current.cache.insert(identify, new usercache(), null, cache.noabsoluteexpiration, new timespan(0, 20, 0), cacheitempriority.high, cacheremovedcallback); } return httpcontext.current.cache.get(identify) as usercache; // as是一种强制类型转化的方式 } /// <summary> /// 缓存被移除时触发 /// </summary> /// <param name="key">被移除的缓存的key</param> /// <param name="value">被移除的缓存的值</param> /// <param name="reason">移除原因</param> private static void cacheremovedcallback(string key, object value, cacheitemremovedreason reason) { // 缓存被移除时执行的操作 // 如果是手动移除,则不处理 //if (reason == cacheitemremovedreason.removed) // return; // 此处访问页面会报错,暂时注释掉 // shownotification(messagetype.warning, "警告", "由于您太久没操作页面已过期,请重新登录!", true); } #endregion } }
依次定义了getcache(),setcache(),removecache(),clearcache()四个方法,供外界调用,来实现对缓存的操作。
到这里,基本上关于这个cache的实现就已经讲完了,下面,给出一段代码,做一个使用的示例。
private const string loginuserkey = "cachekey-loginusercachekey"; /// <summary> /// 获取或设置当前登录用户 /// </summary> public static user loginuser { get { return webcache.getcache(loginuserkey) as user; } set { webcache.setcache(loginuserkey, value); } }
setcache():
webcache.setcache(key, value);
removecache():
removecache(key); //移除字典中某个缓存值
clearcache();
clearcache(); //清空缓存的字典
关于这个缓存设计,就记录到这里了,关于.net core下的实现,因为.net core下并没有system.web这个类,所以它的cache实现方式,与.net 下的实现方式有些区别,这个,我会另起一篇博客去记录。
说明:本片博客并没有完整的demo,所有的代码都已贴出。