.NET 缓存模块设计

上一篇谈了我对缓存的概念,框架上的理解和看法,这篇承接上篇讲讲我自己的缓存模块设计实践。

基本的缓存模块设计

最基础的缓存模块一定有一个统一的CacheHelper,如下:

    public interface ICacheHelper
    {
        T Get<T>(string key);

        void Set<T>(string key, T value);       

        void Remove(string key);
    }

然后业务层是这样调用的

        public User Get(int id)
        {
            if (id <= 0)
                throw new ArgumentNullException("id");

            var key = string.Format(USER_CACHE_KEY, id);
            var user = _cacheHelper.Get<User>(key);
            if (user != null)
                return user;

            return _repository.Get(id);
        }    

上面的代码没什么错误,但是实际运用的时候就产生疑问了,因为我一直强调缓存要保存"热数据",那样"热数据"一定会有过期的时候,我们不可能另外写一个去Set。所以干脆就结合到一起写是比较合适的。

public User GetV2(int id)
{
    if (id <= 0)
       throw new ArgumentNullException("id");

    var key = string.Format(USER_CACHE_KEY, id);
    var user = _cacheHelper.Get<User>(key);
    if (user != null)
       return user;
        user = _repository.Get(id);
    if (user != null)
        _cacheHelper.Set(key, user);
        return user;
}

上面的代码其实只是加了一个Set而已,就这样的设计的话,每次一个Get需要的重复代码实在是太多了,那么是不是应该更精简?这时候吃点C#语法糖就很有必要了,语法糖偶尔吃点增进效率,何乐而不为?

public User GetV3(int id)
{
      if (id <= 0)
          throw new ArgumentNullException("id");

      var key = string.Format(USER_CACHE_KEY, id);
       return _cacheHelperV2.Get<User>(key, () => _repository.Get(id));
}

//ICache Get<T>实现
public T Get<T>(string key, Func<T> fetch = null)
{
    T result = default(T);
    var obj = Cache.Get(key);
    if (obj is T)
    {
        result = (T)obj;
    }

    if(result == null)
    {
        result = fetch();

        if (result != null)
            Set(key, result);
    }

    return result;
}            

这里我直接把Set方法都包装进了ICache.Get<T>,附带上Fetch Func。这样就把公共的操作抽象到了一起,简化了Cache的调用,完美的符合了我的想法。

缓存模块设计进阶

上一节里的ICache V3几乎已经最精简了,但是其实参考了ServiceStack.Redis之后,我发现了更加的抽象方式。很明显上一节的所有代码里,都是手动管理Key的,对于通常的对象Cache,这个Key还需要手动吗?来上最后一份改进。

public T Get<T>(object id, Func<T> fetch = null)
{
    var type = typeof(T);
    var key = string.Format("urn:{1}:{2}", type.Name, id.ToString());//这里是关键,直接用TypeName来充当Key

    return Get(key, fetch);
}

public T Get<T>(string key, Func<T> fetch = null)
{
    T result = default(T);

    var obj = Cache.Get(key);
    if (obj is T)
    {
        result = (T)obj;
    }

    if (result == null)
    {
        result = fetch();

        if (result != null)
           Set(key, result);
     }

     return result;
}

Get方法完全自动化管理了Key,然后调用的方式再次被精简。

public User GetV4(int id)
{
     if (id <= 0)
        throw new ArgumentNullException("id");

     return _cacheHelperV3.Get<User>(id, () => _repository.Get(id));
}

很明显还少了最重要的Set啊,Set的时候这个Key获取就要费一点事情了,最需要 解决的是如何获取这个主键id的值。

public class User
{
        [PrimaryKey] //这个Attribute是最重要的东西
        public int UserId { get; set;}

        public string UserName { get; set; }

        public string Cellphone { get; set; }
}
public void Set<T>(T obj)
{
      //此处应该被缓存以提高反射的效率
      var type = typeof(T);
      var primaryKey = type.GetProperties()
                .FirstOrDefault(t => t.GetCustomAttributes(false)
                    .Any(c => c is PrimaryKeyAttribute));//这里通过取PrimaryKeyAttribute来获取ID的value
       var keyValue = primaryKey.GetValue(obj, null);
        var key = string.Format("urn:{0}:{1}", type.Name, keyValue);

       var dt = DateTime.UtcNow.AddDays(1);//假设默认缓存1天
        var offset = new DateTimeOffset(dt);
        Cache.Set(key, obj, offset);
}

到这里,我想到的最终版本的ICache就完成了。这里还需要说明的是其实PrimaryKey可以更加灵活多变。很多时候一个Object的PrimaryKey是很复杂的,这时候设计Cache实体的时候可以变通下:

public class UserCacheEntity
{
        [PrimaryKey]
        public int ID
        {
            get
            {
                return string.Format("{0}:{1}", UserId, UserName);
            }
        }

        public int UserId { get; set; }

        public string UserName { get; set; }

        public string Cellphone { get; set; }
}

上面的方式几乎可以自动管理常见的数据Cache了,唯一麻烦的是 需要自定义一个CacheObject,这样就带来了实体转换的麻烦,这时候就要看怎么取舍了。

再次说明下我想要的ICache设计:

1. 永远只Cache热数据,这意味着每个Key都要有过期时间

2. ICache自动管理Get/Set,最好能自动管理Key。

3. ICache精简同时又不失灵活。

详细的代码Demo可以参考:Git

更灵活的实现

我在写这篇总结之前,也一直在思考Cache应该放到什么层,普通三层的时候放哪里?DDD那样分层的时候又放哪里。Google了下,看到了一些参考。

http://stackoverflow.com/questions/15340173/in-which-layer-implement-the-cache

我觉得这里比较符合我的想法,Cache应该是全局任意的,当然实现起来当然是interface+IOC,这样引用起来更加的独立一些。

另外还有Cache更加高级的使用,AOP结合ICache V4这样的设计,岂不是更好?这里我还没有去实现AOP的Attribute,这又是一个大话题的,下次再来实现吧。

本文比较粗陋,欢迎大家拍砖,期待共同进步。

时间: 2024-08-07 07:44:34

.NET 缓存模块设计的相关文章

缓存模块设计

NET 缓存模块设计 上一篇谈了我对缓存的概念,框架上的理解和看法,这篇承接上篇讲讲我自己的缓存模块设计实践. 基本的缓存模块设计 最基础的缓存模块一定有一个统一的CacheHelper,如下: public interface ICacheHelper { T Get<T>(string key); void Set<T>(string key, T value); void Remove(string key); } 然后业务层是这样调用的 public User Get(in

IOS编程 图片缓存模块设计

手机客户端为什么会留存下来?而不是被一味的Wap替代掉?因为手机客户端有Wap无可替代的优势,就是自身较强的计算能力. 手机中不可避免的一环:图片缓存,在软件的整个运行过程中显得尤为重要. 先简单说一下图片缓存的作用: 提高响应速度 减少网络流量 提高用户体验 提高响应速度:因为图片一旦缓存在本地之后,那么本地IO数据的读取,远比网络中得IO读取效率要高的多.所以可以提高响应速度 减少网络流量:一张图片在某些情况下,只加载一次,之后便不会重新加载,减少了网络流量.减少流量肯定是必然的.介于国内的

框架模块设计经验总结

转自:http://www.cnblogs.com/zgynhqf/archive/2011/07/15/2107593.html 这是原创,尊重原创............ 框架模块设计经验总结 三个月没写日志了,比较懒散--下半年准备做OEA 的 B/S 版本,比较复杂,需要从架构设计开始认真入手.正好今天到了部门反思的时间,今天先把原来的一些设计经验总结一下,以方便将来回顾. 直入主题,这篇日志主要用于总结一些框架级别的模块设计经验. 总述 一个大型的框架,必然由多个较独立的子系统/子模块

模块设计与实现经验总结(三)

3  模块详细设计指南与规范 模块详细设计要完成两个方面工作:一是明确模块的功能需求和非功能需求.二是设计如何完成和实现模块的功能需求,包括类结构.线程结构设计等.本节根据后台模块特点,描述了两部分工作需要考虑和设计的关键点. 3.1确定模块的功能规格 1) 本模块概述 概述主要描述了本模块所属子系统,以及在子系统中所承当职责的简单描述. 2) 本模块在系统中与周围模块关系和交互情况 很多模块一般要依赖周围的模块或者数据库,为此建议以图形方式描述本模块与本模块依赖的其他模块或者数据库之间交互情况

nginx三 之缓存模块

友情提示: 缓存模块是在动静分离的环境基础上搭建,动静分离可以参考http://www.cnblogs.com/dahuandan/p/6759212.html 介绍 提高网站响应速度是web应用不容忽视的目标,在之前动静分离的基础上,我们已经降低了后端服务器压力,提高了处理请求的性能,但是用户请求的静态资源是从硬盘读取,相比内存的性能还有很大的提高: Nginx自带的缓存模块可以把静态资源缓存到内存中,提高了用户请求静态资源的速度,并且nginx自带缓存模块配置简单,使用灵活,搭配第三方插件可

app模块设计

至于app模块设计,要坚持三个原则: 1.放羊,让用户决定模块间的组合与穿插. 2.滥竽充数,对于用户不希望的模块,可以悄悄植入以实现产品目标. 3.照葫芦画瓢,遵守用户在其它APP上的既有习惯,组合各个模块和布置页面内容.工具.操作. 目前,依照以上原则,突出产品特色,忽略次要因素,大概搭建了app的几大模块,如图

常见的缓存算法设计策略

对于缓存,大家应该都不会感到陌生,但是关于缓存算法有哪些,大家可能不会太清楚,这里我大概介绍下. 缓存的设计目的就是为了我们访问方便,减少访问时间,大体上有这四种策略: 一:基于时间的策略.当缓存未满的时候,一直向缓存区添加,当缓存区满的时候,再有数据进来,就需要将以访问过的数据清除掉. 清除的就是那些访问时间久的数据.说白了就是访问时间距离现在越远的将首先被淘汰. 二:基于频率的策略.当缓冲区满的时候,按照访问频率将数据进行排序,将那些访问频率较少的数据淘汰掉. 三:基于时间和频率的策略.当缓

atitit。浏览器缓存机制 and 微信浏览器防止缓存的设计 attilax 总结

atitit.浏览器缓存机制 and 微信浏览器防止缓存的设计 attilax 总结 1. 缓存的一些机制 1 1.1. http 304 1 1.2. 浏览器刷新的处理机制 1 1.3. Expires 2 1.4. Cache-Control 2 1.5. Last-Modified/E-tag 3 1.6. Etag 主要为了解决 Last-Modified 无法解决的一些问题. 4 2. 不同的页面打开方式产生的请求区别 5 3. html  meta法 5 4. http head 法

2017.7.1 慕课网-Java从零打造企业级电商项目实战:用户模块设计与开发

2. 用户模块设计与开发 2.1 要实现的功能 2.2 mmall_user表 2.3 用户模块接口设计 (1)门户-用户接口 http://git.oschina.net/imooccode/happymmallwiki/wikis/%E9%97%A8%E6%88%B7_%E7%94%A8%E6%88%B7%E6%8E%A5%E5%8F%A3 (2)后台-用户接口 http://git.oschina.net/imooccode/happymmallwiki/wikis/%E5%90%8E%E