Discuz!NT中集成Memcached分布式缓存 我想真正等到Velocity能堪当重任还要等上一段时间。于是我就开始将注意力转移到了Memcached,必定有Facebook这只“超级小白鼠”使用它并且反响还不错。所以就开始尝试动手在产品中集成Memcached。其实在之前的那篇关于Discuz!NT缓存架构的文章中已提到过使用了设计模式中的“策略模式”来构造。所以为了与以往使用缓存的代码格式相兼容所以这里采用新添加MemCachedStrategy(MemCached策略)来构造一个缓存策略类以便于当管理后台开启“MemCached”时以“MemCached策略模式”来做为当前系统默认的策略模式。其代码段如下Discuz.Cache/MemCached.cs/// summary/// MemCache缓存策略类/// /summarypublic class MemCachedStrategy : Discuz.Cache.ICacheStrategy{/// summary/// 添加指定ID的对象/// /summary/// param nameobjId/param/// param nameo/parampublic void AddObject(string objId, object o){RemoveObject(objId);if (TimeOut 0){MemCachedManager.CacheClient.Set(objId, o, System.DateTime.Now.AddMinutes(TimeOut));}else{MemCachedManager.CacheClient.Set(objId, o);}}/// summary/// 添加指定ID的对象(关联指定文件组)/// /summary/// param nameobjId/param/// param nameo/param/// param namefiles/parampublic void AddObjectWithFileChange(string objId, object o, string[] files){;}/// summary/// 添加指定ID的对象(关联指定键值组)/// /summary/// param nameobjId/param/// param nameo/param/// param namedependKey/parampublic void AddObjectWithDepend(string objId, object o, string[] dependKey){;}/// summary/// 移除指定ID的对象/// /summary/// param nameobjId/parampublic void RemoveObject(string objId){if (MemCachedManager.CacheClient.KeyExists(objId))MemCachedManager.CacheClient.Delete(objId);}/// summary/// 返回指定ID的对象/// /summary/// param nameobjId/param/// returns/returnspublic object RetrieveObject(string objId){return MemCachedManager.CacheClient.Get(objId);}/// summary/// 到期时间/// /summarypublic int TimeOut { set; get; }}上面类实现的接口Discuz.Cache.ICacheStrategy定义如下/// summary/// 公共缓存策略接口/// /summarypublic interface ICacheStrategy{/// summary/// 添加指定ID的对象/// /summary/// param nameobjId/param/// param nameo/paramvoid AddObject(string objId, object o);/// summary/// 添加指定ID的对象(关联指定文件组)/// /summary/// param nameobjId/param/// param nameo/param/// param namefiles/paramvoid AddObjectWithFileChange(string objId, object o, string[] files);/// summary/// 添加指定ID的对象(关联指定键值组)/// /summary/// param nameobjId/param/// param nameo/param/// param namedependKey/paramvoid AddObjectWithDepend(string objId, object o, string[] dependKey);/// summary/// 移除指定ID的对象/// /summary/// param nameobjId/paramvoid RemoveObject(string objId);/// summary/// 返回指定ID的对象/// /summary/// param nameobjId/param/// returns/returnsobject RetrieveObject(string objId);/// summary/// 到期时间/// /summaryint TimeOut { set;get;}}当然在MemCachedStrategy类中还有一个对象要加以说明就是MemCachedManager该类主要是对Memcached一些常操作和相关初始化实例调用的“封装”下面是是其变量定义和初始化构造方法的代码/// summary/// MemCache管理操作类/// /summarypublic sealed class MemCachedManager{#region 静态方法和属性private static MemcachedClient mc null;private static SockIOPool pool null;private static MemCachedConfigInfo memCachedConfigInfo MemCachedConfigs.GetConfig();private static string [] serverList null;static MemCachedManager(){CreateManager();}private static void CreateManager(){serverList Utils.SplitString(memCachedConfigInfo.ServerList, rn);pool SockIOPool.GetInstance(memCachedConfigInfo.PoolName);pool.SetServers(serverList);pool.InitConnections memCachedConfigInfo.IntConnections;//初始化链接数pool.MinConnections memCachedConfigInfo.MinConnections;//最少链接数pool.MaxConnections memCachedConfigInfo.MaxConnections;//最大连接数pool.SocketConnectTimeout memCachedConfigInfo.SocketConnectTimeout;//Socket链接超时时间pool.SocketTimeout memCachedConfigInfo.SocketTimeout;// Socket超时时间pool.MaintenanceSleep memCachedConfigInfo.MaintenanceSleep;//维护线程休息时间pool.Failover memCachedConfigInfo.FailOver; //失效转移(一种备份操作模式)pool.Nagle memCachedConfigInfo.Nagle;//是否用nagle算法启动socketpool.HashingAlgorithm HashingAlgorithm.NewCompatibleHash;pool.Initialize();mc new MemcachedClient();mc.PoolName memCachedConfigInfo.PoolName;mc.EnableCompression false;}/// summary/// 缓存服务器地址列表/// /summarypublic static string[] ServerList{set{if (value ! null)serverList value;}get { return serverList; }}/// summary/// 客户端缓存操作对象/// /summarypublic static MemcachedClient CacheClient{get{if (mc null)CreateManager();return mc;}}public static void Dispose(){if (pool ! null)pool.Shutdown();}上面代码中构造方法会初始化一个池来管理执行Socket链接并提供静态属性CacheClient以便MemCachedStrategy来调用。当然我还在这个管理操作类中添加了几个方法分别用于检测当前有效的分布式缓存服务器的列表向指定或全部缓存服务器发送特定stats命令来获取当前缓存服务器上的数据信息和内存分配信息等相应的方法如下详情见注释/// summary/// 获取当前缓存键值所存储在的服务器/// /summary/// param namekey当前缓存键/param/// returns当前缓存键值所存储在的服务器/returnspublic static string GetSocketHost(string key){string hostName ;SockIO sock null;try{sock SockIOPool.GetInstance(memCachedConfigInfo.PoolName).GetSock(key);if (sock ! null){hostName sock.Host;}}finally{if (sock ! null)sock.Close();}return hostName;}/// summary/// 获取有效的服务器地址/// /summary/// returns有效的服务器地/returnspublic static string[] GetConnectedSocketHost(){SockIO sock null;string connectedHost null;foreach (string hostName in serverList){if (!Discuz.Common.Utils.StrIsNullOrEmpty(hostName)){try{sock SockIOPool.GetInstance(memCachedConfigInfo.PoolName).GetConnection(hostName);if (sock ! null){connectedHost Discuz.Common.Utils.MergeString(hostName, connectedHost);}}finally{if (sock ! null)sock.Close();}}}return Discuz.Common.Utils.SplitString(connectedHost, ,);}/// summary/// 获取服务器端缓存的数据信息/// /summary/// returns返回信息/returnspublic static ArrayList GetStats(){ArrayList arrayList new ArrayList();foreach (string server in serverList){arrayList.Add(server);}return GetStats(arrayList, Stats.Default, null);}/// summary/// 获取服务器端缓存的数据信息/// /summary/// param nameserverArrayList要访问的服务列表/param/// returns返回信息/returnspublic static ArrayList GetStats(ArrayList serverArrayList, Stats statsCommand, string param){ArrayList statsArray new ArrayList();param Utils.StrIsNullOrEmpty(param) ? : param.Trim().ToLower();string commandstr stats;//转换stats命令参数switch (statsCommand){case Stats.Reset: { commandstr stats reset; break; }case Stats.Malloc: { commandstr stats malloc; break; }case Stats.Maps: { commandstr stats maps; break; }case Stats.Sizes: { commandstr stats sizes; break; }case Stats.Slabs: { commandstr stats slabs; break; }case Stats.Items: { commandstr stats; break; }case Stats.CachedDump:{string[] statsparams Utils.SplitString(param, );if(statsparams.Length 2)if(Utils.IsNumericArray(statsparams))commandstr stats cachedump param;break;}case Stats.Detail:{if(string.Equals(param, on) || string.Equals(param, off) || string.Equals(param, dump))commandstr stats detail param.Trim();break;}default: { commandstr stats; break; }}//加载返回值Hashtable stats MemCachedManager.CacheClient.Stats(serverArrayList, commandstr);foreach (string key in stats.Keys){statsArray.Add(key);Hashtable values (Hashtable)stats[key];foreach (string key2 in values.Keys){statsArray.Add(key2 : values[key2]);}}return statsArray;}/// summary/// Stats命令行参数/// /summarypublic enum Stats{/// summary/// stats : 显示服务器信息, 统计数据等/// /summaryDefault 0,/// summary/// stats reset : 清空统计数据/// /summaryReset 1,/// summary/// stats malloc : 显示内存分配数据/// /summaryMalloc 2,/// summary/// stats maps : 显示/proc/self/maps数据/// /summaryMaps 3,/// summary/// stats sizes/// /summarySizes 4,/// summary/// stats slabs : 显示各个slab的信息,包括chunk的大小,数目,使用情况等/// /summarySlabs 5,/// summary/// stats items : 显示各个slab中item的数目和最老item的年龄(最后一次访问距离现在的秒数)/// /summaryItems 6,/// summary/// stats cachedump slab_id limit_num : 显示某个slab中的前 limit_num 个 key 列表/// /summaryCachedDump 7,/// summary/// stats detail [on|off|dump] : 设置或者显示详细操作记录 on:打开详细操作记录 off:关闭详细操作记录 dump: 显示详细操作记录(每一个键值get,set,hit,del的次数)/// /summaryDetail 8}当然在配置初始化缓存链接池时使用了配置文件方式memcached.config来管理相关参数其info信息类说明如下Discuz.Config/MemCachedConfigInfo.cs/// summary/// MemCached配置信息类文件/// /summarypublic class MemCachedConfigInfo : IConfigInfo{private bool _applyMemCached;/// summary/// 是否应用MemCached/// /summarypublic bool ApplyMemCached{get{return _applyMemCached;}set{_applyMemCached value;}}private string _serverList;/// summary/// 链接地址/// /summarypublic string ServerList{get{return _serverList;}set{_serverList value;}}private string _poolName;/// summary/// 链接池名称/// /summarypublic string PoolName{get{return Utils.StrIsNullOrEmpty(_poolName) ? DiscuzNT_MemCache : _poolName;}set{_poolName value;}}private int _intConnections;/// summary/// 初始化链接数/// /summarypublic int IntConnections{get{return _intConnections 0 ? _intConnections : 3;}set{_intConnections value;}}private int _minConnections;/// summary/// 最少链接数/// /summarypublic int MinConnections{get{return _minConnections 0 ? _minConnections : 3;}set{_minConnections value;}}private int _maxConnections;/// summary/// 最大连接数/// /summarypublic int MaxConnections{get{return _maxConnections 0 ?_maxConnections : 5;}set{_maxConnections value;}}private int _socketConnectTimeout;/// summary/// Socket链接超时时间/// /summarypublic int SocketConnectTimeout{get{return _socketConnectTimeout 1000 ? _socketConnectTimeout : 1000;}set{_socketConnectTimeout value;}}private int _socketTimeout;/// summary/// socket超时时间/// /summarypublic int SocketTimeout{get{return _socketTimeout 1000 ? _maintenanceSleep : 3000;}set{_socketTimeout value;}}private int _maintenanceSleep;/// summary/// 维护线程休息时间/// /summarypublic int MaintenanceSleep{get{return _maintenanceSleep 0 ? _maintenanceSleep : 30;}set{_maintenanceSleep value;}}private bool _failOver;/// summary/// 链接失败后是否重启,详情参见http://baike.baidu.com/view/1084309.htm/// /summarypublic bool FailOver{get{return _failOver;}set{_failOver value;}}private bool _nagle;/// summary/// 是否用nagle算法启动socket/// /summarypublic bool Nagle{get{return _nagle;}set{_nagle value;}}}这些参数我们通过注释应该有一些了解可以说memcached的主要性能都是通过这些参数来决定的大家应根据自己公司产品和应用的实际情况配置相应的数值。当然做完这一步之后就是对调用“缓存策略”的主体类进行修改来使其根据对管理后台的设计来决定加载什么样的缓存策略如下/// summary/// Discuz!NT缓存类/// 对Discuz!NT论坛缓存进行全局控制管理/// /summarypublic class DNTCache{.//通过该变量决定是否启用MemCachedprivate static bool applyMemCached MemCachedConfigs.GetConfig().ApplyMemCached;/// summary/// 构造函数/// /summaryprivate DNTCache(){if (applyMemCached)cs new MemCachedStrategy();else{cs new DefaultCacheStrategy();objectXmlMap rootXml.CreateElement(Cache);//建立内部XML文档.rootXml.AppendChild(objectXmlMap);//LogVisitor clv new CacheLogVisitor();//cs.Accept(clv);cacheConfigTimer.AutoReset true;cacheConfigTimer.Enabled true;cacheConfigTimer.Elapsed new System.Timers.ElapsedEventHandler(Timer_Elapsed);cacheConfigTimer.Start();}}到这里主要的开发和修改基本上就告一段落了。下面开始介绍一下如果使用Stats命令来查看缓存的分配和使用等情况。之前在枚举类型Stats中看到该命令有几个主要的参数分别是statsstats resetstats mallocstats mapsstats sizesstats slabsstats itemsstats cachedump slab_id limit_numstats detail [on|off|dump]而JAVAEYE的 robbin 写过一篇文章贴一段遍历memcached缓存对象的小脚本来介绍如何使用其中的“stats cachedump”来获取信息。受这篇文章的启发我将MemCachedClient.cs文件中的Stats方法加以修改添加了一个command参数字符串型)这样就可以向缓存服务器发送上面所说的那几种类型的命令了。测试代码如下protected void Submit_Click(object sender, EventArgs e){ArrayList arrayList new ArrayList();arrayList.Add(10.0.1.52:11211);//缓存服务器的地址StateResult.DataSource MemCachedManager.GetStats(arrayList, (MemCachedManager.Stats)Utils.StrToInt(StatsParam.SelectedValue, 0), Param.Text);StateResult.DataBind();}页面代码如下我这样做的目的有两个一个是避免每次都使用telnet协议远程登陆缓存服务器并输入相应的命令行