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
   Tags: discuz!nt, memcached, redis,llserver,key/value db


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值