在开发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
生气是拿别人做错的事来惩罚自己