Discuz!NT中的LLServer架构设计

长平狐 发布于 2012/11/06 18:41
阅读 78
收藏 0

     在开发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

 


 


原文链接:http://www.cnblogs.com/daizhj/archive/2011/08/26/discuznt_llserver_arch.html
加载中
返回顶部
顶部