zoukankan      html  css  js  c++  java
  • Discuz!NT中的Redis架构设计

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

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

    复制代码
    /// <summary>
    /// 企业级Redis缓存策略类
    /// </summary>
    public class RedisStrategy : DefaultCacheStrategy
    {
        /// <summary>
        /// 添加指定ID的对象
        /// </summary>
        /// <param name="objId"></param>
        /// <param name="o"></param>
        public override void AddObject(string objId, object o)
        {  
            if (!objId.StartsWith("/Forum/ShowTopic/"))
                base.AddObject(objId, o, LocalCacheTime);

            using (IRedisClient Redis = RedisManager.GetClient())
            {
                Redis.Set<byte[]>(objId, new ObjectSerializer().Serialize(o));
            }
        }

        /// <summary>
        /// 加入当前对象到缓存中
        /// </summary>
        /// <param name="objId">对象的键值</param>
        /// <param name="o">缓存的对象</param>
        /// <param name="o">到期时间,单位:秒</param>
        public override void AddObject(string objId, object o, int expire)
        {
            //凡是以"/Forum/ShowTopic/"为前缀不添加到本地缓存中,现类似键值包括: "/Forum/ShowTopic/Tag/{topicid}/" , "/Forum/ShowTopic/TopList/{fid}"
            if (!objId.StartsWith("/Forum/ShowTopic/"))
                base.AddObject(objId, o, expire);

            using (IRedisClient Redis = RedisManager.GetClient())
            {
                //永不过期
                if (expire == 0)
                    Redis.Set<byte[]>(objId, new ObjectSerializer().Serialize(o));
                else
                    Redis.Set<byte[]>(objId, new ObjectSerializer().Serialize(o), DateTime.Now.AddSeconds(expire));
            }         
       }


        /// <summary>
        /// 移除指定ID的对象
        /// </summary>
        /// <param name="objId"></param>
        public override void RemoveObject(string objId)
        {
            //先移除本地cached,然后再移除memcached中的相应数据
            base.RemoveObject(objId);
            using (IRedisClient Redis = RedisManager.GetClient())
            {
                Redis.Remove(objId);
            }
            Discuz.EntLib.SyncCache.SyncRemoteCache(objId);
        }      

        public override object RetrieveObject(string objId)
        {
            object obj = base.RetrieveObject(objId);

            if (obj == null)
            {
                using (IRedisClient Redis = RedisManager.GetClient())
                {
                    obj = new ObjectSerializer().Deserialize(Redis.Get<byte[]>(objId));

                    if (obj != null && !objId.StartsWith("/Forum/ShowTopic/"))//对ShowTopic页面缓存数据不放到本地缓存
                    {
                        if (objId.StartsWith("/Forum/ShowTopicGuestCachePage/"))//对游客缓存页面ShowTopic数据缓存设置有效时间
                            base.TimeOut = GeneralConfigs.GetConfig().Guestcachepagetimeout * 60;
                        if (objId.StartsWith("/Forum/ShowForumGuestCachePage/"))//对游客缓存页面ShowTopic数据缓存设置有效时间
                            base.TimeOut = RedisConfigs.GetConfig().CacheShowForumCacheTime * 60;
                        else
                            base.TimeOut = LocalCacheTime;

                        base.AddObject(objId, obj, TimeOut);
                    }                
                }
            }
            return obj;
        }

        /// <summary>
        /// 到期时间,单位:秒
        /// </summary>
        public override int TimeOut
        {
            get
            {
                return 3600;
            }
        }

        /// <summary>
        /// 本地缓存到期时间,单位:秒
        /// </summary>
        public int LocalCacheTime
        {
            get
            {
                return RedisConfigs.GetConfig().LocalCacheTime;
            }
        }

        /// <summary>
        /// 清空的有缓存数据
        /// </summary>
        public override void FlushAll()
        {
            base.FlushAll();
            using (IRedisClient Redis = RedisManager.GetClient())
            {
                Redis.FlushAll();
            }
        }
    }
    复制代码


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

    复制代码
    /// <summary>
    /// Redis配置信息类文件
    /// </summary>
    public class RedisConfigInfo : IConfigInfo
    {
        private bool _applyRedis;
        /// <summary>
        /// 是否应用Redis
        /// </summary>
        public bool ApplyRedis
        {
            get
            {
                return _applyRedis;
            }
            set
            {
                _applyRedis = value;
            }
        }

        private string _writeServerList;
        /// <summary>
        /// 可写的Redis链接地址
        /// </summary>
        public string WriteServerList
        {
            get
            {
                return _writeServerList;
            }
            set
            {
                _writeServerList = value;
            }
        }

        private string _readServerList;
        /// <summary>
        /// 可读的Redis链接地址
        /// </summary>
        public string ReadServerList
        {
            get
            {
                return _readServerList;
            }
            set
            {
                _readServerList = value;
            }
        }

        private int _maxWritePoolSize;
        /// <summary>
        /// 最大写链接数
        /// </summary>
        public int MaxWritePoolSize
        {
            get
            {
                return _maxWritePoolSize > 0 ? _maxWritePoolSize : 5;
            }
            set
            {
                _maxWritePoolSize = value;
            }
        }

        private int _maxReadPoolSize;
        /// <summary>
        /// 最大读链接数
        /// </summary>
        public int MaxReadPoolSize
        {
            get
            {
                return _maxReadPoolSize > 0 ? _maxReadPoolSize : 5;
            }
            set
            {
                _maxReadPoolSize = value;
            }
        }

        private bool _autoStart;
        /// <summary>
        /// 自动重启
        /// </summary>
        public bool AutoStart
        {
            get
            {
                return _autoStart;
            }
            set
            {
                _autoStart = value;
            }
        }
        

        private int _localCacheTime = 30000;
        /// <summary>
        /// 本地缓存到期时间,该设置会与memcached搭配使用,单位:秒
        /// </summary>
        public int LocalCacheTime
        {
            get
            {
                return _localCacheTime;
            }
            set
            {
                _localCacheTime = value;
            }
        }

        private bool _recordeLog = false;
        /// <summary>
        /// 是否记录日志,该设置仅用于排查redis运行时出现的问题,如redis工作正常,请关闭该项
        /// </summary>
        public bool RecordeLog
        {
            get
            {
                return _recordeLog;
            }
            set
            {
                _recordeLog = value;
            }
        }

        private int _cacheShowTopicPageNumber = 5;
        /// <summary>
        /// 缓存帖子列表分页数(showtopic页数使用缓存前N页的帖子列表信息)
        /// </summary>
        public int CacheShowTopicPageNumber
        {
            get
            {
                return _cacheShowTopicPageNumber;
            }
            set
            {
                _cacheShowTopicPageNumber = value;
            }
        }

        /// <summary>
        /// 缓存showforum页面分页数
        /// </summary>
        public int CacheShowForumPageNumber{set;get;}

        /// <summary>
        /// 缓存showforum页面时间(单位:分钟)
        /// </summary>
        public int CacheShowForumCacheTime{set;get;}
    }
    复制代码

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

    复制代码
    <?xml version="1.0"?>
    <RedisConfigInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <ApplyRedis>true</ApplyRedis>
      <WriteServerList>10.0.4.210:6379</WriteServerList>
      <ReadServerList>10.0.4.210:6379</ReadServerList>
      <MaxWritePoolSize>60</MaxWritePoolSize>
      <MaxReadPoolSize>60</MaxReadPoolSize>
      <AutoStart>true</AutoStart>
      <LocalCacheTime>180</LocalCacheTime>
      <!--单位:秒-->
      <RecordeLog>false</RecordeLog>
      <!--缓存帖子列表分页数(showtopic页数使用缓存前N页的帖子列表信息)-->
      <CacheShowTopicPageNumber>2</CacheShowTopicPageNumber>
      <!--缓存showforum页面分页数-->
      <CacheShowForumPageNumber>2</CacheShowForumPageNumber>
      <!--缓存showforum页面时间(单位:分钟)-->
      <CacheShowForumCacheTime>10</CacheShowForumCacheTime>
    </RedisConfigInfo>
    复制代码



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

    复制代码
    using System.Collections;
    using Discuz.Config;
    using Discuz.Common;

    using ServiceStack.Redis;
    using ServiceStack.Redis.Generic;
    using ServiceStack.Redis.Support;

    namespace Discuz.EntLib
    {
        /// <summary>
        /// MemCache管理操作类
        /// </summary>
        public sealed class RedisManager
        {
            /// <summary>
            /// redis配置文件信息
            /// </summary>
            private static RedisConfigInfo redisConfigInfo = RedisConfigs.GetConfig();

            private static PooledRedisClientManager prcm;

            /// <summary>
            /// 静态构造方法,初始化链接池管理对象
            /// </summary>
            static RedisManager()
            {
                CreateManager();
            }


            /// <summary>
            /// 创建链接池管理对象
            /// </summary>
            private static void CreateManager()
            {
                string[] writeServerList = Utils.SplitString(redisConfigInfo.WriteServerList, ",");
                string[] readServerList = Utils.SplitString(redisConfigInfo.ReadServerList, ",");

                prcm = new PooledRedisClientManager(readServerList, writeServerList,
                                 new RedisClientManagerConfig
                                 {
                                     MaxWritePoolSize = redisConfigInfo.MaxWritePoolSize,
                                     MaxReadPoolSize = redisConfigInfo.MaxReadPoolSize,
                                     AutoStart = redisConfigInfo.AutoStart,
                                 });           
            }

            /// <summary>
            /// 客户端缓存操作对象
            /// </summary>
            public static IRedisClient GetClient()
            {
                if (prcm == null)
                    CreateManager();

                return prcm.GetClient();
            }
        }
    }
    复制代码



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

  • 相关阅读:
    团队开发冲刺2.3(2015.5.27)
    团队开发冲刺2.2(2015.5.26)
    团队开发冲刺2.1(2015.5.26)
    团队开发冲刺1.6(2015.5.14)
    团队开发冲刺1.5(2015.5.13)
    团队开发冲刺1.4(2015.5.12)
    团队开发冲刺1.3(2015.5.11)
    团队开发冲刺1.2(2015.5.10)
    团队开发冲刺1.1(2015.5.9)
    找1
  • 原文地址:https://www.cnblogs.com/Alex80/p/5548235.html
Copyright © 2011-2022 走看看