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", false, true));        }        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(0, 0);        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(0, 0);        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

生气是拿别人做错的事来惩罚自己

Discuz!NT 中的LLServer架构设计

相关文章:

你感兴趣的文章:

标签云: