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

 


 

posted @ 2011-08-26 11:13 代震军 阅读(...) 评论(...) 编辑 收藏