在开发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>
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。
注:有关llserver的安装使用信息请参见如下链接:
http://www.cnblogs.com/daizhj/archive/2011/08/23/2150422.html
下面介绍一下我们企业版中LLSERVER的客户端的设计思路。熟悉我们产品的朋友知道我们的缓存设计基于stratety模式,之前的memcached,redis都有相应的策略实现,详情参见下面两个链接:
Discuz!NT中进行缓存分层(本地缓存+memcached)
这里对LLServer也不例外,同样引入了相应的策略实现,如下:
Discuz.EntLib\LLServer\LLManager.cs
顾名思义,LLStrategy.cs即是策略实现,LLManager.cs只是一个访问LLServer服务端的一个客户端封装。下面分别看一下源代码,首先是LLStrategy.cs:
/// 企业级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文件的代码:
/// 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文件中的
并开启memcached.config文件中的
选项即可,但同时也要设置该文件的“<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