• Discuz!NT中的LLServer架构设计


         在开发LLServer的同时,我一直在跟进测试企业版的相应LLServer客户端,目前这部分代码已测试完毕并提交的Discuz!NT产品中,会跟随最新的源码包一并发布。本文主要是介绍一下产品中引入LLServer的架构思路。

         在Discuz!NT的企业版产品中,使用了Memcached,Redis这两个软件来提供分布式缓存服务(两者任选其一)。现有又有了LLServer,它不仅提供了KEY/VALUE缓存,还包括持久化存储部分。这样,用户可以有更多大的选择余地。

         下面是Discuz!NT的企业版分布式缓存中一个架构图(DNTCache用于包含调用cacheStrategy):


         我们通过配置相应的config文件来决定使用那种类型的缓存服务。当然其也有一个优先顺序,即:memcached, redis, llserver。这可以参照最新版的DNTCache.cs文件(位于Discuz.Cache项目下),部分代码参见如下:

    /// <summary>
    /// 构造函数
    /// </summary>
    private DNTCache()
    {
        
    if (MemCachedConfigs.GetConfig() != null && MemCachedConfigs.GetConfig().ApplyMemCached)
            applyMemCached 
    = true;
        
    if (RedisConfigs.GetConfig() != null && RedisConfigs.GetConfig().ApplyRedis)
            applyRedis 
    = true;
        
    if (LLServerConfigs.GetConfig() != null && LLServerConfigs.GetConfig().ApplyLLServer)
            applyLLServer 
    = true;

        
    if (applyMemCached || applyRedis || applyLLServer)
        {
            
    try
            {
                
    string cacheStratetyName;
                
    if(applyMemCached)
                    cacheStratetyName 
    = "MemCachedStrategy";
                
    else if(applyRedis)
                    cacheStratetyName 
    = "RedisStrategy";
                
    else
                    cacheStratetyName 
    = "LLStrategy";

                cs 
    = cachedStrategy = (ICacheStrategy)Activator.CreateInstance(Type.GetType("Discuz.EntLib." + cacheStratetyName + ", Discuz.EntLib"falsetrue));
            }
            
    catch
            {
                
    throw new Exception("请检查Discuz.EntLib.dll文件是否被放置在bin目录下并配置正确");
            }
        }
        
    else
        {
            cs 
    = new DefaultCacheStrategy();
            
    if (rootXml.HasChildNodes)
                rootXml.RemoveAll();

            objectXmlMap 
    = rootXml.CreateElement("Cache");
            
    //建立内部XML文档.
            rootXml.AppendChild(objectXmlMap);
        }    
        
    }


         当memcached.config及redis.config文件的Apply..选项为false时,这时如启用llserver.config文件的如下节点,即可启动llserver。
        

    <ApplyLLServer>true</ApplyLLServer>

         注:有关llserver的安装使用信息请参见如下链接:
         http://www.cnblogs.com/daizhj/archive/2011/08/23/2150422.html

         下面介绍一下我们企业版中LLSERVER的客户端的设计思路。熟悉我们产品的朋友知道我们的缓存设计基于stratety模式,之前的memcached,redis都有相应的策略实现,详情参见下面两个链接:

         Discuz!NT中的Redis架构设计 

         Discuz!NT中进行缓存分层(本地缓存+memcached)

         这里对LLServer也不例外,同样引入了相应的策略实现,如下:

      Discuz.EntLib\LLServer\LLStrategy.cs
      Discuz.EntLib\LLServer\LLManager.cs

         顾名思义,LLStrategy.cs即是策略实现,LLManager.cs只是一个访问LLServer服务端的一个客户端封装。下面分别看一下源代码,首先是LLStrategy.cs:

    /// <summary>
    /// 企业级llserver缓存策略类
    /// </summary>
    public class LLStrategy : 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);
        
            LLManager.Set(objId, o);
            RecordLog(objId, 
    "set");
        }

        
    /// <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);

            LLManager.Set(objId, o, expire);
            RecordLog(objId, 
    "set");
        }
        
       

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

        
    /// <summary>
        
    /// 获取指定 key 的对象
        
    /// </summary>
        
    /// <param name="objId">对象的键值</param>
        public override object RetrieveObject(string objId)
        {
            
    object obj = base.RetrieveObject(objId);

            
    if (obj == null)
            {               
                obj 
    = LLManager.Get(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 = LLServerConfigs.GetConfig().CacheShowForumCacheTime * 60;
                    
    else
                        
    base.TimeOut = LocalCacheTime;

                    
    base.AddObject(objId, obj, TimeOut);
                }
                RecordLog(objId, 
    "get");

            }
            
    return obj;
        }

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

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

        
    /// <summary>
        
    /// 清空的有缓存数据
        
    /// </summary>
        public override void FlushAll()
        {
            
    base.FlushAll();
            LLManager.DeleteAll();
        }
    }

         代码比较简单,大家看一下注释就可以了。下面是LLManager.cs文件的代码:

    /// <summary>
    /// MemCache管理操作类
    /// </summary>
    public sealed class LLManager
    {
        
    /// <summary>
        
    /// redis配置文件信息
        
    /// </summary>
        private static LLServerConfigInfo llConfigInfo = LLServerConfigs.GetConfig();

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

        
    /// <summary>
        
    /// 转换 .NET 日期为 UNIX 时间戳
        
    /// </summary>
        
    /// <param name="expire">到期时间,单位:秒</param>
        
    /// <returns></returns>
        private static int GetExpirationUnixTime(int expire)
        {
            
    if (expire <= 0)
                
    return 0;

            DateTime expiration 
    = DateTime.Now.AddSeconds(expire);
            
    if (expiration <= DateTime.Now)
                
    return 0;

            
    return Discuz.Common.UnixDateTimeHelper.ConvertToUnixTimestamp(expiration);
        }

        
    private static readonly BinaryFormatter bf = new BinaryFormatter();
        
    /// <summary>
        
    ///  Serialize object to buffer
        
    /// </summary>
        
    /// <param name="value">serializable object</param>
        
    /// <returns></returns>
        public static byte[] Serialize(object value)
        {
            
    if (value == null)
                
    return null;
            var memoryStream 
    = new MemoryStream();
            memoryStream.Seek(
    00);
            bf.Serialize(memoryStream, value);
            
    return memoryStream.ToArray();
        }

        
    /// <summary>
        
    ///     Deserialize buffer to object
        
    /// </summary>
        
    /// <param name="someBytes">byte array to deserialize</param>
        
    /// <returns></returns>
        public static object Deserialize(byte[] someBytes)
        {
            
    if (someBytes == null)
                
    return null;
            var memoryStream 
    = new MemoryStream();
            memoryStream.Write(someBytes, 
    0, someBytes.Length);
            memoryStream.Seek(
    00);
            
    return bf.Deserialize(memoryStream);
        }

        
    public static string ToBase64(byte[] binBuffer)
        {
            
    int base64ArraySize = (int)Math.Ceiling(binBuffer.Length / 3d) * 4;
            
    char[] charBuffer = new char[base64ArraySize];
            Convert.ToBase64CharArray(binBuffer, 
    0, binBuffer.Length, charBuffer, 0);
            
    return new string(charBuffer);
        }

        
    /// <summary>
        
    /// 将Base64编码文本转换成Byte[]
        
    /// </summary>
        
    /// <param name="base64">Base64编码文本</param>
        
    /// <returns></returns>
        public static Byte[] Base64ToBytes(string base64)
        {
            
    char[] charBuffer = base64.ToCharArray();
            
    return Convert.FromBase64CharArray(charBuffer, 0, charBuffer.Length);
        }

        
    /// <summary>
        
    /// 获取指定 key 的对象
        
    /// </summary>
        
    /// <param name="t">对象的键值</param>
        
    /// <param name="objId">对象的键值</param>
        public static object Get(string objId)
        {
            
    string result = Utils.GetHttpWebResponse(llConfigInfo.ServerList + "opt=get&charset=utf-8&key=" + objId, "GET"null);

            
    if (result == null || result.EndsWith("ERROR"))
                
    return null;
            
    else
                
    return Deserialize(Base64ToBytes(result.Substring(0, result.IndexOf("$$END$$"))));
        }

        
    /// <summary>
        
    /// 设置对象到缓存中
        
    /// </summary>
        
    /// <param name="objId">对象的键值</param>
        
    /// <param name="data">缓存的对象</param>
        public static bool Set(string objId, object data)
        {
            
    return Set(objId, data, 0);
        }

        
    /// <summary>
        
    /// 设置对象到缓存中
        
    /// </summary>
        
    /// <param name="objId">对象的键值</param>
        
    /// <param name="o">缓存的对象</param>
        
    /// <param name="exptime">到期时间,单位:秒</param>
        public static bool Set(string objId, object data, int exptime)
        {
            exptime 
    = GetExpirationUnixTime(exptime);
            
    string result = Utils.UrlEncode(ToBase64(Serialize(data))) + "$$END$$";
            result 
    = Utils.GetHttpWebResponse(
                             
    string.Format("{0}opt=put&charset=utf-8&key={1}{2}&length={3}",
                                          llConfigInfo.ServerList,
                                          objId,
                                          exptime 
    > 0 ? "&exptime=" + exptime : "",
                                          result.Length),
                               
    "POST",
                               result);
            
    return result != null && !result.EndsWith("ERROR");
        }

        
    /// <summary>
        
    /// 客户端缓存操作对象
        
    /// </summary>
        public static bool Delete(string objId)
        {
            
    string result = Utils.GetHttpWebResponse(llConfigInfo.ServerList + "opt=delete&charset=utf-8&key=" + objId, "GET"null);
            
    return result != null && !result.EndsWith("ERROR");
        }

        
    /// <summary>
        
    /// 获取所有对象,暂时未实现非http协议功能
        
    /// </summary>
        public static string GetAll()
        {
            
    string result = Utils.GetHttpWebResponse(llConfigInfo.ServerList + "opt=getlist&charset=utf-8""GET"null);
            
    if (result == null || result.EndsWith("ERROR")) 
                
    return null;
            
    else
                
    return result;          
        }

        
    /// <summary>
        
    /// 删除所有缓存对象
        
    /// </summary>
        public static bool DeleteAll()
        {
            
    string result = Utils.GetHttpWebResponse(llConfigInfo.ServerList + "opt=deleteall&charset=utf-8""GET"null);
            
    if (result == null || result.EndsWith("ERROR"))
                
    return false;
            
    else
                
    return true;
        }        


     

        LLManager.cs类主要是以封装了以http协议方式访问llserver的操作(因为llserver可支持http协议和memcached socket协议)。该类有两个地方需要注意:
        1.使用base64对value部分进行编码,以解决object对象二进制序列化后llserver无法存储的问题(llserver这个问题将会在后续版本中解决)
        2.在set/get操作时,对value结尾添加“$$END$$”标识来告之数据在该标识位结束。

        了解了这些内容之后,最后再说一个企业版中使用memcached socket协议连接llserver的情况。因为之前企业版中已使用了memcached,这里如果要使用llserver,只须关闭当前llserver.config文件中的

    <ApplyLLServer>false</ApplyLLServer>

         并开启memcached.config文件中的

    <ApplyMemCached>true</ApplyMemCached>

         选项即可,但同时也要设置该文件的“<ApplyBase64>true</ApplyBase64>”节点,这样就可以用该memcached client来链接使用llserver了。


       好了,到这里今天的内容就先告一段落了。

       原文链接:http://www.cnblogs.com/daizhj/archive/2011/08/26/discuznt_llserver_arch.html
       作者: daizhj, 代震军  
       微博: http://weibo.com/daizhj
       Tags: discuz!nt, memcached, redis,llserver,key/value db


     

  • 相关阅读:
    hive 之with....as的用法
    hive 之lateral view 函数用法
    linux 之 bash: telnet: command not found...报错
    mysql 之 一个库中所有表复制到另一个数据库中,实现两个表迁移
    mysql 之group by 后取每一组最大的一行值
    kettle 报错 Error converting characters into server's character set. Some character(s) could not be converted.
    mysql 之数据类型转换cast()函数和convert()函数
    hive 之基础知识及查询三
    hive 之基础知识及语法二
    Vue(十)
  • 原文地址:https://www.cnblogs.com/daizhj/p/discuznt_llserver_arch.html
Copyright © 2020-2023  润新知