在之前的Discuz!NT缓存的架构方案中,曾说过Discuz!NT采用了两级缓存方式,即本地缓存+memcached方式。在近半年多的实际运行环境下,该方案经受住了检验。现在为了提供多样式的解决方案,我在企业版里引入了Redis这个目前炙手可热的缓存架构产品,即将memcached与Redis作为可选插件方式来提供了最终用户,尽管目前测试的结果两者的差异不是很大(毫秒级),但我想多一种选择对用户来说也是好的。

闲话不多说了,开始今天的正文吧。
    
     熟悉我们产品的开发者都知道,我们的产品在缓存设计上使用了策略模式(Stratety Pattern),之前在系统中就已实现了DefaultCacheStrategy和MemCachedStrategy,前者用于本地缓存方式,后者顾名思义,就是memcached缓存方式。所以只要我们实现一个RedisStrategy策略,并适当修改加载缓存策略的代码,就可以将memcached缓存方式替换成Redis,如下图:
      
 
     下面我先将RedisStrategy的部分代码放上来,大家一看便知:

  1.  
  2. /// <summary>
  3. /// 企业级Redis缓存策略类
  4. /// </summary>
  5. public class RedisStrategy : DefaultCacheStrategy
  6. {
  7.     /// <summary>
  8.     /// 添加指定ID的对象
  9.     /// </summary>
  10.     /// <param name="objId"></param>
  11.     /// <param name="o"></param>
  12.     public override void AddObject(string objId, object o)
  13.     {  
  14.         if (!objId.StartsWith("/Forum/ShowTopic/"))
  15.             base.AddObject(objId, o, LocalCacheTime);
  16.  
  17.         using (IRedisClient Redis = RedisManager.GetClient())
  18.         {
  19.             Redis.Set<byte[]>(objId, new ObjectSerializer().Serialize(o));
  20.         }
  21.     }
  22.  
  23.     /// <summary>
  24.     /// 加入当前对象到缓存中
  25.     /// </summary>
  26.     /// <param name="objId">对象的键值</param>
  27.     /// <param name="o">缓存的对象</param>
  28.     /// <param name="o">到期时间,单位:秒</param>
  29.     public override void AddObject(string objId, object o, int expire)
  30.     {
  31.         //凡是以"/Forum/ShowTopic/"为前缀不添加到本地缓存中,现类似键值包括: "/Forum/ShowTopic/Tag/{topicid}/" , "/Forum/ShowTopic/TopList/{fid}"
  32.         if (!objId.StartsWith("/Forum/ShowTopic/"))
  33.             base.AddObject(objId, o, expire);
  34.  
  35.         using (IRedisClient Redis = RedisManager.GetClient())
  36.         {
  37.             //永不过期
  38.             if (expire == 0)
  39.                 Redis.Set<byte[]>(objId, new ObjectSerializer().Serialize(o));
  40.             else
  41.                 Redis.Set<byte[]>(objId, new ObjectSerializer().Serialize(o), DateTime.Now.AddSeconds(expire));
  42.         }         
  43.    }
  44.  
  45.     /// <summary>
  46.     /// 移除指定ID的对象
  47.     /// </summary>
  48.     /// <param name="objId"></param>
  49.     public override void RemoveObject(string objId)
  50.     {
  51.         //先移除本地cached,然后再移除memcached中的相应数据
  52.         base.RemoveObject(objId);
  53.         using (IRedisClient Redis = RedisManager.GetClient())
  54.         {
  55.             Redis.Remove(objId);
  56.         }
  57.         Discuz.EntLib.SyncCache.SyncRemoteCache(objId);
  58.     }      
  59.  
  60.     public override object RetrieveObject(string objId)
  61.     {
  62.         object obj = base.RetrieveObject(objId);
  63.  
  64.         if (obj == null)
  65.         {
  66.             using (IRedisClient Redis = RedisManager.GetClient())
  67.             {
  68.                 obj = new ObjectSerializer().Deserialize(Redis.Get<byte[]>(objId));
  69.  
  70.                 if (obj != null && !objId.StartsWith("/Forum/ShowTopic/"))//对ShowTopic页面缓存数据不放到本地缓存
  71.                 {
  72.                     if (objId.StartsWith("/Forum/ShowTopicGuestCachePage/"))//对游客缓存页面ShowTopic数据缓存设置有效时间
  73.                         base.TimeOut = GeneralConfigs.GetConfig().Guestcachepagetimeout * 60;
  74.                     if (objId.StartsWith("/Forum/ShowForumGuestCachePage/"))//对游客缓存页面ShowTopic数据缓存设置有效时间
  75.                         base.TimeOut = RedisConfigs.GetConfig().CacheShowForumCacheTime * 60;
  76.                     else
  77.                         base.TimeOut = LocalCacheTime;
  78.  
  79.                     base.AddObject(objId, obj, TimeOut);
  80.                 }                
  81.             }
  82.         }
  83.         return obj;
  84.     }
  85.  
  86.     /// <summary>
  87.     /// 到期时间,单位:秒
  88.     /// </summary>
  89.     public override int TimeOut
  90.     {
  91.         get
  92.         {
  93.             return 3600;
  94.         }
  95.     }
  96.  
  97.     /// <summary>
  98.     /// 本地缓存到期时间,单位:秒
  99.     /// </summary>
  100.     public int LocalCacheTime
  101.     {
  102.         get
  103.         {
  104.             return RedisConfigs.GetConfig().LocalCacheTime;
  105.         }
  106.     }
  107.  
  108.     /// <summary>
  109.     /// 清空的有缓存数据
  110.     /// </summary>
  111.     public override void FlushAll()
  112.     {
  113.         base.FlushAll();
  114.         using (IRedisClient Redis = RedisManager.GetClient())
  115.         {
  116.             Redis.FlushAll();
  117.         }
  118.     }
  119. }

可以看出RedisStrategy类继承自DefaultCacheStrategy,这一点与MemCachedStrategy实现如出一辙,唯一不同是其缓存数据加载和设置的方式有所不同,而具体的用法可以参见我之前写的这篇文章中的“object序列化方式存储”  。
    
     当然上面代码中有两个类未说明,一个是RedisConfigs,一个是RedisManager,前者是配置文件信息类,我们产品因为使用了统一的序列化接口实现方式(参见该文),所以其实现方式比较清晰,其序列化类的结构如下:

  1.  
  2. /// <summary>
  3. /// Redis配置信息类文件
  4. /// </summary>
  5. public class RedisConfigInfo : IConfigInfo
  6. {
  7.     private bool _applyRedis;
  8.     /// <summary>
  9.     /// 是否应用Redis
  10.     /// </summary>
  11.     public bool ApplyRedis
  12.     {
  13.         get
  14.         {
  15.             return _applyRedis;
  16.         }
  17.         set
  18.         {
  19.             _applyRedis = value;
  20.         }
  21.     }
  22.  
  23.     private string _writeServerList;
  24.     /// <summary>
  25.     /// 可写的Redis链接地址
  26.     /// </summary>
  27.     public string WriteServerList
  28.     {
  29.         get
  30.         {
  31.             return _writeServerList;
  32.         }
  33.         set
  34.         {
  35.             _writeServerList = value;
  36.         }
  37.     }
  38.  
  39.     private string _readServerList;
  40.     /// <summary>
  41.     /// 可读的Redis链接地址
  42.     /// </summary>
  43.     public string ReadServerList
  44.     {
  45.         get
  46.         {
  47.             return _readServerList;
  48.         }
  49.         set
  50.         {
  51.             _readServerList = value;
  52.         }
  53.     }
  54.  
  55.     private int _maxWritePoolSize;
  56.     /// <summary>
  57.     /// 最大写链接数
  58.     /// </summary>
  59.     public int MaxWritePoolSize
  60.     {
  61.         get
  62.         {
  63.             return _maxWritePoolSize > 0 ? _maxWritePoolSize : 5;
  64.         }
  65.         set
  66.         {
  67.             _maxWritePoolSize = value;
  68.         }
  69.     }
  70.  
  71.     private int _maxReadPoolSize;
  72.     /// <summary>
  73.     /// 最大读链接数
  74.     /// </summary>
  75.     public int MaxReadPoolSize
  76.     {
  77.         get
  78.         {
  79.             return _maxReadPoolSize > 0 ? _maxReadPoolSize : 5;
  80.         }
  81.         set
  82.         {
  83.             _maxReadPoolSize = value;
  84.         }
  85.     }
  86.  
  87.     private bool _autoStart;
  88.     /// <summary>
  89.     /// 自动重启
  90.     /// </summary>
  91.     public bool AutoStart
  92.     {
  93.         get
  94.         {
  95.             return _autoStart;
  96.         }
  97.         set
  98.         {
  99.             _autoStart = value;
  100.         }
  101.     }
  102.     
  103.  
  104.     private int _localCacheTime = 30000;
  105.     /// <summary>
  106.     /// 本地缓存到期时间,该设置会与memcached搭配使用,单位:秒
  107.     /// </summary>
  108.     public int LocalCacheTime
  109.     {
  110.         get
  111.         {
  112.             return _localCacheTime;
  113.         }
  114.         set
  115.         {
  116.             _localCacheTime = value;
  117.         }
  118.     }
  119.  
  120.     private bool _recordeLog = false;
  121.     /// <summary>
  122.     /// 是否记录日志,该设置仅用于排查redis运行时出现的问题,如redis工作正常,请关闭该项
  123.     /// </summary>
  124.     public bool RecordeLog
  125.     {
  126.         get
  127.         {
  128.             return _recordeLog;
  129.         }
  130.         set
  131.         {
  132.             _recordeLog = value;
  133.         }
  134.     }
  135.  
  136.     private int _cacheShowTopicPageNumber = 5;
  137.     /// <summary>
  138.     /// 缓存帖子列表分页数(showtopic页数使用缓存前N页的帖子列表信息)
  139.     /// </summary>
  140.     public int CacheShowTopicPageNumber
  141.     {
  142.         get
  143.         {
  144.             return _cacheShowTopicPageNumber;
  145.         }
  146.         set
  147.         {
  148.             _cacheShowTopicPageNumber = value;
  149.         }
  150.     }
  151.  
  152.     /// <summary>
  153.     /// 缓存showforum页面分页数
  154.     /// </summary>
  155.     public int CacheShowForumPageNumber{set;get;}
  156.  
  157.     /// <summary>
  158.     /// 缓存showforum页面时间(单位:分钟)
  159.     /// </summary>
  160.     public int CacheShowForumCacheTime{set;get;}
  161. }

其序列化出来的xml文件格式形如:

  1.  
  2. <?xml version="1.0"?>
  3. <RedisConfigInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  4.   <ApplyRedis>true</ApplyRedis>
  5.   <WriteServerList>10.0.4.210:6379</WriteServerList>
  6.   <ReadServerList>10.0.4.210:6379</ReadServerList>
  7.   <MaxWritePoolSize>60</MaxWritePoolSize>
  8.   <MaxReadPoolSize>60</MaxReadPoolSize>
  9.   <AutoStart>true</AutoStart>
  10.   <LocalCacheTime>180</LocalCacheTime>
  11.   <!--单位:秒-->
  12.   <RecordeLog>false</RecordeLog>
  13.   <!--缓存帖子列表分页数(showtopic页数使用缓存前N页的帖子列表信息)-->
  14.   <CacheShowTopicPageNumber>2</CacheShowTopicPageNumber>
  15.   <!--缓存showforum页面分页数-->
  16.   <CacheShowForumPageNumber>2</CacheShowForumPageNumber>
  17.   <!--缓存showforum页面时间(单位:分钟)-->
  18.   <CacheShowForumCacheTime>10</CacheShowForumCacheTime>
  19. </RedisConfigInfo>

之前所说的RedisManager类则是一个RedisClient客户端实现的简单封装,主要为了简化基于链接池的Redis链接方式的使用。其结构如下:

  1.  
  2. using System.Collections;
  3. using Discuz.Config;
  4. using Discuz.Common;
  5.  
  6. using ServiceStack.Redis;
  7. using ServiceStack.Redis.Generic;
  8. using ServiceStack.Redis.Support;
  9.  
  10. namespace Discuz.EntLib
  11. {
  12.     /// <summary>
  13.     /// MemCache管理操作类
  14.     /// </summary>
  15.     public sealed class RedisManager
  16.     {
  17.         /// <summary>
  18.         /// redis配置文件信息
  19.         /// </summary>
  20.         private static RedisConfigInfo redisConfigInfo = RedisConfigs.GetConfig();
  21.  
  22.         private static PooledRedisClientManager prcm;
  23.  
  24.         /// <summary>
  25.         /// 静态构造方法,初始化链接池管理对象
  26.         /// </summary>
  27.         static RedisManager()
  28.         {
  29.             CreateManager();
  30.         }
  31.  
  32.         /// <summary>
  33.         /// 创建链接池管理对象
  34.         /// </summary>
  35.         private static void CreateManager()
  36.         {
  37.             string[] writeServerList = Utils.SplitString(redisConfigInfo.WriteServerList, ",");
  38.             string[] readServerList = Utils.SplitString(redisConfigInfo.ReadServerList, ",");
  39.  
  40.             prcm = new PooledRedisClientManager(readServerList, writeServerList,
  41.                              new RedisClientManagerConfig
  42.                              {
  43.                                  MaxWritePoolSize = redisConfigInfo.MaxWritePoolSize,
  44.                                  MaxReadPoolSize = redisConfigInfo.MaxReadPoolSize,
  45.                                  AutoStart = redisConfigInfo.AutoStart,
  46.                              });           
  47.         }
  48.  
  49.         /// <summary>
  50.         /// 客户端缓存操作对象
  51.         /// </summary>
  52.         public static IRedisClient GetClient()
  53.         {
  54.             if (prcm == null)
  55.                 CreateManager();
  56.  
  57.             return prcm.GetClient();
  58.         }
  59.     }
  60. }

上面的代码主要将redis.config的配置文件文件信息加载到程序里并实始化PooledRedisClientManager对象,该对象用于池化redis的客户端链接,具体方式参见这篇文章
        
      好了,到这里主要的内容就介绍完了。

Discuz!NT中的Redis架构设计的更多相关文章

  1. Redis --> Redis架构设计

    Redis架构设计 一.前言   Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件. 它支持多种类型的数据结构,如 字符串(strings), 散列 ...

  2. 细说分布式Redis架构设计和踩过的那些坑

    细说分布式Redis架构设计和踩过的那些坑_redis 分布式_ redis 分布式锁_分布式缓存redis 细说分布式Redis架构设计和踩过的那些坑

  3. Java生鲜电商平台-生鲜系统中微服务架构设计与分析实战

    Java生鲜电商平台-生鲜系统中微服务架构设计与分析实战 说明: Java生鲜系统中微服务的拆分应该如何架构设计与分析呢?以下是我的实战中的设计与经验分析. 目录 1. 微服务简介2. 当前现状3. ...

  4. Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲

    Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲 Java生鲜电商平台:   微服务是当前非常流行的技术框架,通过服务的小型化.原子化以及分布式架构的弹性伸缩和高可用性, ...

  5. Discuz!NT中集成Memcached分布式缓存

    大约在两年前我写过一篇关于Discuz!NT缓存架构的文章,在那篇文章的结尾介绍了在IIS中如果开启多个应用程序池会造成多个缓存实例之间数据同步的问题.虽然给出了一个解决方案,但无形中却把压力转移到了 ...

  6. [转载] Codis作者黄东旭细说分布式Redis架构设计和踩过的那些坑们

    原文: http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=208733458&idx=1&sn=691bfde670fb ...

  7. 浅谈iOS中MVVM的架构设计与团队协作

    说到架构设计和团队协作,这个对App的开发还是比较重要的.即使作为一个专业的搬砖者,前提是你这砖搬完放在哪?不只是Code有框架,其他的东西都是有框架的,比如桥梁等等神马的~在这儿就不往外扯了.一个好 ...

  8. iOS中MVVM的架构设计与团队协作

    对MVVM的理解主要是借鉴于之前的用过的MVC的Web框架,之前用过ThinkPHP框架,和SSH框架,都是MVC的架构模式,今天MVVM与传统的MVC可谓是极为相似,也可以说是兄弟关系,也就是一家人 ...

  9. IOS中 浅谈iOS中MVVM的架构设计与团队协作

    今天写这篇文章是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇文章的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

随机推荐

  1. 文档翻译-Minimizing your app's Memory Footprint

    原文地址:https://developer.apple.com/library/ios/technotes/tn2434/_index.html#//apple_ref/doc/uid/DTS400 ...

  2. 【Linux】设定一个能输入中文的英文环境!

    引子:centos startx 进入桌面后使用中文输入法 这个解决方法太蠢了,而且只适用于centos等red系系统... 在此提供一个更加通用的方法 => 只要设置好系统的locale坏境变 ...

  3. NSFileManager 沙盒文件管理

    文件夹创建,复制,移动,删除,检查是否存在,代码如下: 1.获取沙盒 document 路径,作为文件夹路径的基路径. NSString *document = NSSearchPathForDire ...

  4. WEB开发人员必知的20+HTML5技巧(转)

    互联网科技发展的速度真可谓惊人的快,一个稍不留神,你就可能无法跟上它的步伐. HTML5的变化和更新也压倒不少人,这篇文章将向大家介绍一些最基本也非常必要的 HTML技巧. 1. 新的文档类型(Doc ...

  5. 【概率】COGS 1487:麻球繁衍

    Description 万有引力定律: “使物体相互靠近的力的大小与物体的质量成正比——而物体的质量又由同一种力决定.这是一个有趣并且有益的例子,说明了科学是如何用A证明B,再用B证明A的.”——安布 ...

  6. Apache URL rewrite 配置

    下面是Apache的配置过程,可以参考下:1.httpd.conf配置文件中加载了mod_rewrite.so模块,使用虚拟主机 #LoadModule rewrite_module modules/ ...

  7. Flume学习——Flume的架构

    Flume有三个组件:Source.Channel 和 Sink.在源码中对应同名的三个接口. When a Flume source receives an event, it stores it ...

  8. ExtJS4 MVC开发教程:搭建开发环境

    原文地址:http://www.lihuai.net/qianduan/extjs/864.html 博主系列教程:http://www.lihuai.net/qianduan/extjs 在所有的J ...

  9. [itint5]字符串匹配

    http://www.itint5.com/oj/#15 用hash来做,目前为止做到最好也是case16超时(20w的规模),即使分桶也超时.注意计算hashcode时,'a'要算成1,否则如果'a ...

  10. POJ3080——Blue Jeans(暴力+字符串匹配)

    Blue Jeans DescriptionThe Genographic Project is a research partnership between IBM and The National ...