Discuz!NT中集成Memcached分布式缓存

大约在两年前我写过一篇关于Discuz!NT缓存架构的文章,在那篇文章的结尾介绍了在IIS中如果开启多个应用程序池会造成多个缓存实例之间数据同步的问题。虽然给出了一个解决方案,但无形中却把压力转移到了磁盘I/O上(多个进程并发访问cache.config文件)。其实从那时起我就开始关注有什么更好的方案,当然今天本文中所说的Memcached,以及Velocity等这类的分布式缓存方案之前都考虑过,但一直未能决定该使用那个。起码Velocity要在.net 4.0之后才会提供,虽然是原生态,但有些远水解不了近火。 我想真正等到Velocity能堪当重任还要等上一段时间。于是我就开始将注意力转移到了Memcached,必定有Facebook这只“超级小白鼠”使用它并且反响还不错。所以就开始尝试动手在产品中集成Memcached。 其实在之前的那篇关于Discuz!NT缓存架构的文章中已提到过,使用了设计模式中的“策略模式”来构造。所以为了与以往使用缓存的代码格式相兼容,所以这里采用新添加MemCachedStrategy(MemCached策略)来构造一个缓存策略类以便于当管理后台开启“MemCached”时以“MemCached策略模式”来做为当前系统默认的策略模式。 其代码段如下(Discuz.Cache/MemCached.cs): ///<summary>///MemCache缓存策略类///</summary>publicclassMemCachedStrategy:Discuz.Cache.ICacheStrategy{///<summary>///添加指定ID的对象///</summary>///<paramname=”objId”></param>///<paramname=”o”></param>publicvoidAddObject(stringobjId,objecto){RemoveObject(objId);if(TimeOut>0){MemCachedManager.CacheClient.Set(objId,o,System.DateTime.Now.AddMinutes(TimeOut));}else{MemCachedManager.CacheClient.Set(objId,o);}}///<summary>///添加指定ID的对象(关联指定文件组)///</summary>///<paramname=”objId”></param>///<paramname=”o”></param>///<paramname=”files”></param>publicvoidAddObjectWithFileChange(stringobjId,objecto,string[]files){;}///<summary>///添加指定ID的对象(关联指定键值组)///</summary>///<paramname=”objId”></param>///<paramname=”o”></param>///<paramname=”dependKey”></param>publicvoidAddObjectWithDepend(stringobjId,objecto,string[]dependKey){;}///<summary>///移除指定ID的对象///</summary>///<paramname=”objId”></param>publicvoidRemoveObject(stringobjId){if(MemCachedManager.CacheClient.KeyExists(objId))MemCachedManager.CacheClient.Delete(objId);}///<summary>///返回指定ID的对象///</summary>///<paramname=”objId”></param>///<returns></returns>publicobjectRetrieveObject(stringobjId){returnMemCachedManager.CacheClient.Get(objId);}///<summary>///到期时间///</summary>publicintTimeOut{set;get;}} 上面类实现的接口Discuz.Cache.ICacheStrategy定义如下: ///<summary>///公共缓存策略接口///</summary>publicinterfaceICacheStrategy{///<summary>///添加指定ID的对象///</summary>///<paramname=”objId”></param>///<paramname=”o”></param>voidAddObject(stringobjId,objecto);///<summary>///添加指定ID的对象(关联指定文件组)///</summary>///<paramname=”objId”></param>///<paramname=”o”></param>///<paramname=”files”></param>voidAddObjectWithFileChange(stringobjId,objecto,string[]files);///<summary>///添加指定ID的对象(关联指定键值组)///</summary>///<paramname=”objId”></param>///<paramname=”o”></param>///<paramname=”dependKey”></param>voidAddObjectWithDepend(stringobjId,objecto,string[]dependKey);///<summary>///移除指定ID的对象///</summary>///<paramname=”objId”></param>voidRemoveObject(stringobjId);///<summary>///返回指定ID的对象///</summary>///<paramname=”objId”></param>///<returns></returns>objectRetrieveObject(stringobjId);///<summary>///到期时间///</summary>intTimeOut{set;get;}} 当然在MemCachedStrategy类中还有一个对象要加以说明,就是MemCachedManager,该类主要是对Memcached一些常操作和相关初始化实例调用的“封装”,下面是是其变量定义和初始化构造方法的代码:///<summary>///MemCache管理操作类///</summary>publicsealedclassMemCachedManager{#region静态方法和属性privatestaticMemcachedClientmc=null;privatestaticSockIOPoolpool=null;privatestaticMemCachedConfigInfomemCachedConfigInfo=MemCachedConfigs.GetConfig();privatestaticstring[]serverList=null;staticMemCachedManager(){CreateManager();}privatestaticvoidCreateManager(){serverList=Utils.SplitString(memCachedConfigInfo.ServerList,””r”n”);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=newMemcachedClient();mc.PoolName=memCachedConfigInfo.PoolName;mc.EnableCompression=false;}///<summary>///缓存服务器地址列表///</summary>publicstaticstring[]ServerList{set{if(value!=null)serverList=value;}get{returnserverList;}}///<summary>///客户端缓存操作对象///</summary>publicstaticMemcachedClientCacheClient{get{if(mc==null)CreateManager();returnmc;}}publicstaticvoidDispose(){if(pool!=null)pool.Shutdown();} 上面代码中构造方法会初始化一个池来管理执行Socket链接,并提供静态属性CacheClient以便MemCachedStrategy来调用。 当然我还在这个管理操作类中添加了几个方法分别用于检测当前有效的分布式缓存服务器的列表,向指定(或全部)缓存服务器发送特定stats命令来获取当前缓存服务器上的数据信息和内存分配信息等,相应的方法如下(详情见注释):///<summary>///获取当前缓存键值所存储在的服务器///</summary>///<paramname=”key”>当前缓存键</param>///<returns>当前缓存键值所存储在的服务器</returns>publicstaticstringGetSocketHost(stringkey){stringhostName=””;SockIOsock=null;try{sock=SockIOPool.GetInstance(memCachedConfigInfo.PoolName).GetSock(key);if(sock!=null){hostName=sock.Host;}}finally{if(sock!=null)sock.Close();}returnhostName;}///<summary>///获取有效的服务器地址///</summary>///<returns>有效的服务器地</returns>publicstaticstring[]GetConnectedSocketHost(){SockIOsock=null;stringconnectedHost=null;foreach(stringhostNameinserverList){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();}}}returnDiscuz.Common.Utils.SplitString(connectedHost,”,”);}///<summary>///获取服务器端缓存的数据信息///</summary>///<returns>返回信息</returns>publicstaticArrayListGetStats(){ArrayListarrayList=newArrayList();foreach(stringserverinserverList){arrayList.Add(server);}returnGetStats(arrayList,Stats.Default,null);}///<summary>///获取服务器端缓存的数据信息///</summary>///<paramname=”serverArrayList”>要访问的服务列表</param>///<returns>返回信息</returns>publicstaticArrayListGetStats(ArrayListserverArrayList,StatsstatsCommand,stringparam){ArrayListstatsArray=newArrayList();param=Utils.StrIsNullOrEmpty(param)?””:param.Trim().ToLower();stringcommandstr=”stats”;//转换stats命令参数switch(statsCommand){caseStats.Reset:{commandstr=”statsreset”;break;}caseStats.Malloc:{commandstr=”statsmalloc”;break;}caseStats.Maps:{commandstr=”statsmaps”;break;}caseStats.Sizes:{commandstr=”statssizes”;break;}caseStats.Slabs:{commandstr=”statsslabs”;break;}caseStats.Items:{commandstr=”stats”;break;}caseStats.CachedDump:{string[]statsparams=Utils.SplitString(param,””);if(statsparams.Length==2)if(Utils.IsNumericArray(statsparams))commandstr=”statscachedump”+param;break;}caseStats.Detail:{if(string.Equals(param,”on”)||string.Equals(param,”off”)||string.Equals(param,”dump”))commandstr=”statsdetail”+param.Trim();break;}default:{commandstr=”stats”;break;}}//加载返回值Hashtablestats=MemCachedManager.CacheClient.Stats(serverArrayList,commandstr);foreach(stringkeyinstats.Keys){statsArray.Add(key);Hashtablevalues=(Hashtable)stats[key];foreach(stringkey2invalues.Keys){statsArray.Add(key2+”:”+values[key2]);}}returnstatsArray;}///<summary>///Stats命令行参数///</summary>publicenumStats{///<summary>///stats:显示服务器信息,统计数据等///</summary>Default=0,///<summary>///statsreset:清空统计数据///</summary>Reset=1,///<summary>///statsmalloc:显示内存分配数据///</summary>Malloc=2,///<summary>///statsmaps:显示”/proc/self/maps”数据///</summary>Maps=3,///<summary>///statssizes///</summary>Sizes=4,///<summary>///statsslabs:显示各个slab的信息,包括chunk的大小,数目,使用情况等///</summary>Slabs=5,///<summary>///statsitems:显示各个slab中item的数目和最老item的年龄(最后一次访问距离现在的秒数)///</summary>Items=6,///<summary>///statscachedumpslab_idlimit_num:显示某个slab中的前limit_num个key列表///</summary>CachedDump=7,///<summary>///statsdetail[on|off|dump]:设置或者显示详细操作记录on:打开详细操作记录off:关闭详细操作记录dump:显示详细操作记录(每一个键值get,set,hit,del的次数)///</summary>Detail=8} 当然在配置初始化缓存链接池时使用了配置文件方式(memcached.config)来管理相关参数,其info信息类说明如下(Discuz.Config/MemCachedConfigInfo.cs):///<summary>///MemCached配置信息类文件///</summary>publicclassMemCachedConfigInfo:IConfigInfo{privatebool_applyMemCached;///<summary>///是否应用MemCached///</summary>publicboolApplyMemCached{get{return_applyMemCached;}set{_applyMemCached=value;}}privatestring_serverList;///<summary>///链接地址///</summary>publicstringServerList{get{return_serverList;}set{_serverList=value;}}privatestring_poolName;///<summary>///链接池名称///</summary>publicstringPoolName{get{returnUtils.StrIsNullOrEmpty(_poolName)?”DiscuzNT_MemCache”:_poolName;}set{_poolName=value;}}privateint_intConnections;///<summary>///初始化链接数///</summary>publicintIntConnections{get{return_intConnections>0?_intConnections:3;}set{_intConnections=value;}}privateint_minConnections;///<summary>///最少链接数///</summary>publicintMinConnections{get{return_minConnections>0?_minConnections:3;}set{_minConnections=value;}}privateint_maxConnections;///<summary>///最大连接数///</summary>publicintMaxConnections{get{return_maxConnections>0?_maxConnections:5;}set{_maxConnections=value;}}privateint_socketConnectTimeout;///<summary>///Socket链接超时时间///</summary>publicintSocketConnectTimeout{get{return_socketConnectTimeout>1000?_socketConnectTimeout:1000;}set{_socketConnectTimeout=value;}}privateint_socketTimeout;///<summary>///socket超时时间///</summary>publicintSocketTimeout{get{return_socketTimeout>1000?_maintenanceSleep:3000;}set{_socketTimeout=value;}}privateint_maintenanceSleep;///<summary>///维护线程休息时间///</summary>publicintMaintenanceSleep{get{return_maintenanceSleep>0?_maintenanceSleep:30;}set{_maintenanceSleep=value;}}privatebool_failOver;///<summary>///链接失败后是否重启,详情参见http://baike.baidu.com/view/1084309.htm///</summary>publicboolFailOver{get{return_failOver;}set{_failOver=value;}}privatebool_nagle;///<summary>///是否用nagle算法启动socket///</summary>publicboolNagle{get{return_nagle;}set{_nagle=value;}}} 这些参数我们通过注释应该有一些了解,可以说memcached的主要性能都是通过这些参数来决定的,大家应根据自己公司产品和应用的实际情况配置相应的数值。 当然,做完这一步之后就是对调用“缓存策略”的主体类进行修改来,使其根据对管理后台的设计来决定加载什么样的缓存策略,如下:///<summary>///Discuz!NT缓存类///对Discuz!NT论坛缓存进行全局控制管理///</summary>publicclassDNTCache{.//通过该变量决定是否启用MemCachedprivatestaticboolapplyMemCached=MemCachedConfigs.GetConfig().ApplyMemCached;///<summary>///构造函数///</summary>privateDNTCache(){if(applyMemCached)cs=newMemCachedStrategy();else{cs=newDefaultCacheStrategy();objectXmlMap=rootXml.CreateElement(“Cache”);//建立内部XML文档.rootXml.AppendChild(objectXmlMap);//LogVisitorclv=newCacheLogVisitor();//cs.Accept(clv);cacheConfigTimer.AutoReset=true;cacheConfigTimer.Enabled=true;cacheConfigTimer.Elapsed+=newSystem.Timers.ElapsedEventHandler(Timer_Elapsed);cacheConfigTimer.Start();}} 到这里,主要的开发和修改基本上就告一段落了。下面开始介绍一下如果使用Stats命令来查看缓存的分配和使用等情况。之前在枚举类型Stats中看到该命令有几个主要的参数,分别是: statsstatsresetstatsmallocstatsmapsstatssizesstatsslabsstatsitemsstatscachedumpslab_idlimit_numstatsdetail[on|off|dump] 而JAVAEYE的 robbin 写过一篇文章:贴一段遍历memcached缓存对象的小脚本,来介绍如何使用其中的 “stats cachedump”来获取信息。受这篇文章的启发,我将MemCachedClient.cs文件中的Stats方法加以修改,添加了一个command参数(字符串型),这样就可以向缓存服务器发送上面所说的那几种类型的命令了。 测试代码如下: protectedvoidSubmit_Click(objectsender,EventArgse){ArrayListarrayList=newArrayList();arrayList.Add(“10.0.1.52:11211”);//缓存服务器的地址StateResult.DataSource=MemCachedManager.GetStats(arrayList,(MemCachedManager.Stats)Utils.StrToInt(StatsParam.SelectedValue,0),Param.Text);StateResult.DataBind();} 页面代码如下:

我这样做的目的有两个,一个是避免每次都使用telnet协议远程登陆缓存服务器并输入相应的命令行参数(我记忆力不好,参数多了之后就爱忘)。二是将来会把这个页面功能内置到管理后台上,以便后台管理员可以动态监测每台缓存服务器上的数据。 好了,到这里今天的内容就差不多了。在本文中我们看到了使用设计模式的好处,通过它我们可以让自己写的代码支持“变化”。这里不妨再多说几句,大家看到了velocity在使用上也是很方便,如果可以的话,未来可以也会将velocity做成一个“缓存策略”,这样站长或管理员就可以根据自己公司的实际情况来加以灵活配置了。 相关资料: memcached 全面剖析.pdf memcached 深度分析

Facebook 对memcached的提升

作者: daizhj, 代震军 网址: http://daizhj.cnblogs.com/

切忌贪婪,恨不得一次玩遍所有传说中的好景点,

Discuz!NT中集成Memcached分布式缓存

相关文章:

你感兴趣的文章:

标签云: