博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Discuz!NT中的LLServer架构设计
阅读量:6721 次
发布时间:2019-06-25

本文共 9113 字,大约阅读时间需要 30 分钟。

     在开发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的安装使用信息请参见如下链接:

    

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

      

    

     这里对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了。

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

   原文链接:

   作者: daizhj, 代震军  
   微博:
   Tags: discuz!nt, memcached, redis,llserver,key/value db

 

 

转载地址:http://iqcmo.baihongyu.com/

你可能感兴趣的文章
Texture tiling and swizzling
查看>>
IOS 真机调试 报错 图片资源 不存在 原因
查看>>
部署NTP服务器进行时间同步
查看>>
Codeforces Round 97B 点分治
查看>>
Candy
查看>>
dN/dS与分子进化常用软件
查看>>
在 foreach 里使用引用要注意的陷阱(转)
查看>>
python3和paramiko安装
查看>>
HDU 2586 How far away ? 离线lca模板题
查看>>
CMarkup 解析XML
查看>>
vue中computed和watch的用法
查看>>
关于Jquery动画滞后问题(转)
查看>>
函数调用约定和堆栈
查看>>
shell编程之运算符
查看>>
[Linux] cscope使用个人笔记
查看>>
JAVA中局部变量 和 成员变量有哪些区别
查看>>
Windows.UI.Cred.dll损坏导致不能设置 PIN 密码
查看>>
数组未退化为指针的三种例外
查看>>
了解External Accessory Framework之EAAccessory
查看>>
每天一道算法题(26)——输入字符串表达式求值
查看>>