您现在的位置是:首页 > cms教程 > Discuz教程Discuz教程

Discuz的NT的LLServer架构设计介绍

钟召云2025-06-24Discuz教程已有人查阅

导读在开发LLServer的同时,我一直在跟进测试企业版的相应LLServer客户端,目前这部分代码已测试完毕并提交的DiscuzNT产品中,会跟随较新的源码包一并发布。

在开发LLServer的同时,我一直在跟进测试企业版的相应LLServer客户端,目前这部分代码已测试完毕并提交的DiscuzNT产品中,会跟随较新的源码包一并发布。本文主要是介绍一下产品中引入LLServer的架构思路。
在DiscuzNT的企业版产品中,使用了Memcached,Redis这两个软件来提供分布式缓存服务(两者任选其一)。现有又有了LLServer,它不仅提供了KEY/VALUE缓存,还包括持久化存储部分。这样,用户可以有更多大的选择余地。
下面是DiscuzNT的企业版分布式缓存中一个架构图(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都有相应的策略实现,详情参见下面两个链接:
DiscuzNT中的Redis架构设计
DiscuzNT中进行缓存分层(本地缓存+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了。
好了,到这里今天的内容就先告一段落了。

本文标签:

很赞哦! ()

相关源码

  • (自适应响应式)高端家用办公家具家居桌椅pbootcms模板下载为办公家具企业设计的响应式网站模板,涵盖产品展示、案例呈现、企业介绍等核心模块。通过可视化后台可快速发布实木桌椅、系统家具、办公屏风等产品信息,帮助客户直观了解材质参数与空间搭配方案。查看源码
  • (自适应手机端)锁锁芯锁具网站pbootcms模板 智能防盗锁网站源码下载本模板基于PbootCMS系统开发,为智能锁具、防盗锁芯及相关安防产品企业设计。采用响应式布局技术,确保在手机、平板和电脑等不同设备上都能获得良好的浏览体验,数据实时同步更新。查看源码
  • 帝国cms淘宝客京东联盟网站整站源码下载本模板基于帝国CMS内核深度开发,为淘宝客行业量身定制。随着腾讯微信与淘宝生态的互联互通,淘宝客链接现可在微信、QQ等平台直接分享,为推广带来更多便利。模板特别优化了店铺推广功能,有效避免商品下架导致的链接失效问题,同时支持京东联盟等多平台商品推广。查看源码
  • (PC+WAP)安保服务保安保镖模板免费下载本模板基于PbootCMS内核开发,为安保服务企业量身打造。设计风格严谨专业,突出安保行业的安全、可靠特性,展示企业服务项目与实力。采用响应式设计,PC与移动端数据同步,管理便捷。模板布局合理查看源码
  • (自适应)大气壁挂炉暖气设备家用电器模板带加盟申请和下载资料为壁挂炉、暖气片等供暖设备企业设计的PbootCMS模板,通过响应式技术实现跨终端展示产品参数和技术细节。后台统一管理确保采暖系统数据、服务网点信息实时同步更新查看源码
  • (自适应响应式)绿色环保材料设备科技类营销型网站pbootcms源码下载本模板基于PbootCMS开发,主要面向环保设备、环保材料及相关科技企业。采用HTML5+CSS3技术构建,具备响应式特性,确保在各类设备上均有良好展示效果。查看源码
分享笔记 (共有 篇笔记)
验证码:

本栏推荐