• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    .NET 缓存模块设计实践

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

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

      public interface ICacheHelper
      {   
        T GetT>(string key);
            
        void SetT>(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.GetUser>(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.GetUser>(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.GetUser>(key, () => _repository.Get(id));      
    }
    
    //ICache GetT>实现
    public T GetT>(string key, FuncT> 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.GetT>,附带上Fetch Func。这样就把公共的操作抽象到了一起,简化了Cache的调用,完美的符合了我的想法。

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

    public T GetT>(object id, FuncT> 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 GetT>(string key, FuncT> 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.GetUser>(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 SetT>(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,这又是一个大话题的,下次再来实现吧。

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    您可能感兴趣的文章:
    • ASP.NET缓存管理的几种方法
    • asp.net(C#)遍历memcached缓存对象
    • Asp.Net Cache缓存使用代码
    • ASP.NET网站管理系统退出 清除浏览器缓存,Session的代码
    • .net/c# memcached缓存获取所有缓存键的方法步骤
    • asp.net 客户端浏览器缓存的Http头介绍
    • ASP.net Substitution 页面缓存而部分不缓存的实现方法
    • ASP.NET性能优化之让浏览器缓存动态网页的方法
    • ASP.NET页面在IE缓存的清除办法
    • asp.net 提高网站速度及如何利用缓存
    上一篇:ASP.NET连接 Access数据库的几种方法
    下一篇:.Net与JS时间日期格式的转换问题对比分析
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    .NET 缓存模块设计实践 .NET,缓存,模块,设计,实践,