似是而非

导航

【转】系统缓存全解析二:动态缓存(4)-Discuz!NT中集成Memcached分布式缓存

Discuz!NT中集成Memcached分布式缓存
文章出处:DIY部落(http://www.diybl.com/course/3_program/cshapo/csharpsl/20100112/189244.html)

其实在之前的那篇关于Discuz!NT缓存架构的文章中已提到过,使用了设计模式中的“策略模式”来构造。所以为了与以往使用缓存的代码格式相兼容,所以这里采用新添加MemCachedStrategy(MemCached策略)来构造一个缓存策略类以便于当管理后台开启“MemCached”时以“MemCached策略模式”来做为当前系统默认
的策略模式。

       其代码段如下(Discuz.Cache/MemCached.cs):

View Code
 1 /// <summary>
 2 /// MemCache缓存策略类
 3 /// </summary>
 4 public class MemCachedStrategy : Discuz.Cache.ICacheStrategy
 5 {
 6 
 7     /// <summary>
 8     /// 添加指定ID的对象
 9     /// </summary>
10     /// <param name="objId"></param>
11     /// <param name="o"></param>
12     public void AddObject(string objId, object o)
13     {
14         RemoveObject(objId);
15         if (TimeOut > 0)
16         {
17             MemCachedManager.CacheClient.Set(objId, o, System.DateTime.Now.AddMinutes(TimeOut));
18         }
19         else
20         {
21             MemCachedManager.CacheClient.Set(objId, o);
22         }
23     }
24 
25     /// <summary>
26     /// 添加指定ID的对象(关联指定文件组)
27     /// </summary>
28     /// <param name="objId"></param>
29     /// <param name="o"></param>
30     /// <param name="files"></param>
31     public void AddObjectWithFileChange(string objId, object o, string[] files)
32     {
33         ;
34     }
35 
36     /// <summary>
37     /// 添加指定ID的对象(关联指定键值组)
38     /// </summary>
39     /// <param name="objId"></param>
40     /// <param name="o"></param>
41     /// <param name="dependKey"></param>
42     public void AddObjectWithDepend(string objId, object o, string[] dependKey)
43     {
44         ;
45     }
46 
47     /// <summary>
48     /// 移除指定ID的对象
49     /// </summary>
50     /// <param name="objId"></param>
51     public void RemoveObject(string objId)
52     {
53         if (MemCachedManager.CacheClient.KeyExists(objId))
54             MemCachedManager.CacheClient.Delete(objId);
55     }
56 
57     /// <summary>
58     /// 返回指定ID的对象
59     /// </summary>
60     /// <param name="objId"></param>
61     /// <returns></returns>
62     public object RetrieveObject(string objId)
63     {
64         return MemCachedManager.CacheClient.Get(objId);
65     }
66 
67     /// <summary>
68     /// 到期时间
69     /// </summary>
70     public int TimeOut { setget; }
71 }

 

       上面类实现的接口Discuz.Cache.ICacheStrategy定义如下: 

View Code
 1 /// <summary>
 2 /// 公共缓存策略接口
 3 /// </summary>
 4 public interface ICacheStrategy
 5 {
 6      /// <summary>
 7      /// 添加指定ID的对象
 8      /// </summary>
 9      /// <param name="objId"></param>
10      /// <param name="o"></param>
11      void AddObject(string objId, object o);
12 
13      /// <summary>
14      /// 添加指定ID的对象(关联指定文件组)
15      /// </summary>
16      /// <param name="objId"></param>
17      /// <param name="o"></param>
18      /// <param name="files"></param>
19      void AddObjectWithFileChange(string objId, object o, string[] files);
20 
21      /// <summary>
22      /// 添加指定ID的对象(关联指定键值组)
23      /// </summary>
24      /// <param name="objId"></param>
25      /// <param name="o"></param>
26      /// <param name="dependKey"></param>
27      void AddObjectWithDepend(string objId, object o, string[] dependKey);
28 
29      /// <summary>
30      /// 移除指定ID的对象
31       /// </summary>
32      /// <param name="objId"></param>
33      void RemoveObject(string objId);
34 
35      /// <summary>
36      /// 返回指定ID的对象
37       /// </summary>
38      /// <param name="objId"></param>
39      /// <returns></returns>
40      object RetrieveObject(string objId);
41 
42      /// <summary>
43      /// 到期时间
44       /// </summary>
45      int TimeOut { set;get;}
46 }

 

      当然在MemCachedStrategy类中还有一个对象要加以说明,就是MemCachedManager,该类主要是对Memcached一些常操作和相关初始化实例调用的“封装”,下面是是其变量定义和初始化构造方法的代码: 

View Code
 1 /// <summary>
 2 /// MemCache管理操作类
 3 /// </summary>
 4 public sealed class MemCachedManager
 5 {
 6     #region 静态方法和属性
 7     private static MemcachedClient mc = null;
 8 
 9     private static SockIOPool pool = null;
10 
11     private static MemCachedConfigInfo memCachedConfigInfo = MemCachedConfigs.GetConfig();
12 
13     private static string [] serverList = null;
14 
15     static MemCachedManager()
16     {
17         CreateManager();
18     }
19 
20     private static void CreateManager()
21     {
22         serverList = Utils.SplitString(memCachedConfigInfo.ServerList, ""r"n");
23 
24         pool = SockIOPool.GetInstance(memCachedConfigInfo.PoolName);
25         pool.SetServers(serverList);
26         pool.InitConnections = memCachedConfigInfo.IntConnections;//初始化链接数
27         pool.MinConnections = memCachedConfigInfo.MinConnections;//最少链接数
28         pool.MaxConnections = memCachedConfigInfo.MaxConnections;//最大连接数
29         pool.SocketConnectTimeout = memCachedConfigInfo.SocketConnectTimeout;//Socket链接超时时间
30         pool.SocketTimeout = memCachedConfigInfo.SocketTimeout;// Socket超时时间
31 
32 pool.MaintenanceSleep = memCachedConfigInfo.MaintenanceSleep;//维护线程休息时间
33         pool.Failover = memCachedConfigInfo.FailOver; //失效转移(一种备份操作模式)
34         pool.Nagle = memCachedConfigInfo.Nagle;//是否用nagle算法启动socket
35         pool.HashingAlgorithm = HashingAlgorithm.NewCompatibleHash;
36         pool.Initialize();
37        
38 
39         mc = new MemcachedClient();
40         mc.PoolName = memCachedConfigInfo.PoolName;
41         mc.EnableCompression = false;
42     }
43 
44     /// <summary>
45     /// 缓存服务器地址列表
46     /// </summary>
47     public static string[] ServerList
48     {
49         set
50         {
51             if (value != null)
52                 serverList = value;
53         }
54         get { return serverList; }
55     }
56 
57     /// <summary>
58     /// 客户端缓存操作对象
59     /// </summary>
60     public static MemcachedClient CacheClient
61     {
62         get
63         {
64             if (mc == null)
65                 CreateManager();
66 
67             return mc;
68         }
69     }
70 
71     public static void Dispose()
72     {
73         if (pool != null)
74             pool.Shutdown();
75     }

 

      上面代码中构造方法会初始化一个池来管理执行Socket链接,并提供静态属性CacheClient以便MemCachedStrategy来调用。

      当然我还在这个管理操作类中添加了几个方法分别用于检测当前有效的分布式缓存服务器的列表,向指定(或全部)缓存服务器发送特定stats命令来获取当前缓存服务器上的数据信息和内存分配信息等,相应的方法如下(详情见注释):

 

View Code
  1 /// <summary>
  2 /// 获取当前缓存键值所存储在的服务器
  3 /// </summary>
  4 /// <param name="key">当前缓存键</param>
  5 /// <returns>当前缓存键值所存储在的服务器</returns>
  6 public static string GetSocketHost(string key)
  7 {
  8     string hostName = "";
  9     SockIO sock = null;
 10     try
 11     {
 12         sock = SockIOPool.GetInstance(memCachedConfigInfo.PoolName).GetSock(key);
 13         if (sock != null)
 14         {
 15             hostName = sock.Host;
 16         }
 17     }
 18     finally
 19     {
 20         if (sock != null)
 21             sock.Close();
 22     }
 23     return hostName;
 24 }
 25 
 26 
 27 /// <summary>
 28 /// 获取有效的服务器地址
 29 /// </summary>
 30 /// <returns>有效的服务器地</returns>
 31 public static string[] GetConnectedSocketHost()
 32 {
 33     SockIO sock = null;
 34     string connectedHost = null;
 35     foreach (string hostName in serverList)
 36     {
 37         if (!Discuz.Common.Utils.StrIsNullOrEmpty(hostName))
 38         {
 39             try
 40             {
 41                 sock = SockIOPool.GetInstance(memCachedConfigInfo.PoolName).GetConnection(hostName);
 42                 if (sock != null)
 43                 {
 44                     connectedHost = Discuz.Common.Utils.MergeString(hostName, connectedHost);
 45                 }
 46             }
 47             finally
 48             {
 49                 if (sock != null)
 50                     sock.Close();
 51             }
 52         }
 53     }
 54     return Discuz.Common.Utils.SplitString(connectedHost, ",");
 55 }
 56 
 57 /// <summary>
 58 /// 获取服务器端缓存的数据信息
 59 /// </summary>
 60 /// <returns>返回信息</returns>
 61 public static ArrayList GetStats()
 62 {
 63     ArrayList arrayList = new ArrayList();
 64     foreach (string server in serverList)
 65     {
 66         arrayList.Add(server);
 67     }
 68     return GetStats(arrayList, Stats.Default, null);
 69 }
 70 
 71 /// <summary>
 72 /// 获取服务器端缓存的数据信息
 73 /// </summary>
 74 /// <param name="serverArrayList">要访问的服务列表</param>
 75 /// <returns>返回信息</returns>
 76 public static ArrayList GetStats(ArrayList serverArrayList, Stats statsCommand, string param)
 77 {
 78     ArrayList statsArray = new ArrayList();
 79     param =Utils.StrIsNullOrEmpty(param)?"":param.Trim().ToLower();
 80 
 81     string commandstr = "stats";
 82     //转换stats命令参数
 83     switch (statsCommand)
 84     {
 85         case Stats.Reset: { commandstr = "stats reset"break; }
 86         case Stats.Malloc: { commandstr = "stats malloc"break; }
 87         case Stats.Maps: { commandstr = "stats maps"break; }
 88         case Stats.Sizes: { commandstr = "stats sizes"break; }
 89         case Stats.Slabs: { commandstr = "stats slabs"break; }
 90         case Stats.Items: { commandstr = "stats"break; }
 91         case Stats.CachedDump:
 92         {
 93             string[] statsparams = Utils.SplitString(param, " ");
 94             if(statsparams.Length == 2)
 95                 if(Utils.IsNumericArray(statsparams))
 96                     commandstr = "stats cachedump " + param;
 97 
 98             break;                     
 99         }
100         case Stats.Detail:
101             {
102                 if(string.Equals(param, "on") || string.Equals(param, "off") || string.Equals(param, "dump"))
103                     commandstr = "stats detail " + param.Trim();
104 
105                 break;
106             }
107         default: { commandstr = "stats"break; }
108     }
109     //加载返回值
110     Hashtable stats = MemCachedManager.CacheClient.Stats(serverArrayList, commandstr);
111     foreach (string key in stats.Keys)
112     {
113         statsArray.Add(key);
114         Hashtable values = (Hashtable)stats[key];
115         foreach (string key2 in values.Keys)
116         {
117             statsArray.Add(key2 + ":" + values[key2]);
118         }
119     }
120     return statsArray;
121 }
122 
123 /// <summary>
124 /// Stats命令行参数
125 /// </summary>
126 public enum Stats
127 {
128     /// <summary>
129     /// stats : 显示服务器信息, 统计数据等
130     /// </summary>
131     Default = 0,
132     /// <summary>
133     /// stats reset : 清空统计数据
134     /// </summary>
135     Reset = 1,
136     /// <summary>
137     /// stats malloc : 显示内存分配数据
138     /// </summary>
139     Malloc = 2,
140     /// <summary>
141     /// stats maps : 显示"/proc/self/maps"数据
142     /// </summary>
143     Maps =3,
144     /// <summary>
145     /// stats sizes
146     /// </summary>
147     Sizes = 4,
148     /// <summary>
149     /// stats slabs : 显示各个slab的信息,包括chunk的大小,数目,使用情况等
150     /// </summary>
151     Slabs = 5,
152     /// <summary>
153 /// stats items : 显示各个slab中item的数目和最老item的年龄(最后一次访问距离现在的秒数)
154     /// </summary>
155     Items = 6,
156     /// <summary>
157     /// stats cachedump slab_id limit_num : 显示某个slab中的前 limit_num 个 key 列表
158     /// </summary>
159     CachedDump =7,
160     /// <summary>
161     /// stats detail [on|off|dump] : 设置或者显示详细操作记录   on:打开详细操作记录 off:关闭详细操作记录 dump: 显示详细操作记录(每一个键值get,set,hit,del的次数)
162     /// </summary>
163     Detail = 8
164 }

 

     当然在配置初始化缓存链接池时使用了配置文件方式(memcached.config)来管理相关参数,其info信息类说明如下(Discuz.Config/MemCachedConfigInfo.cs):

 

View Code
  1 /// <summary>
  2 /// MemCached配置信息类文件
  3 /// </summary>
  4 public class MemCachedConfigInfo : IConfigInfo
  5 {
  6     private bool _applyMemCached;
  7     /// <summary>
  8     /// 是否应用MemCached
  9     /// </summary>
 10     public bool ApplyMemCached
 11     {
 12         get
 13         {
 14             return _applyMemCached;
 15         }
 16         set
 17         {
 18             _applyMemCached = value;
 19         }
 20     }
 21 
 22     private string _serverList;
 23     /// <summary>
 24     /// 链接地址
 25     /// </summary>
 26     public string ServerList
 27     {
 28         get
 29         {
 30             return _serverList;
 31         }
 32         set
 33         {
 34             _serverList = value;
 35         }
 36     }
 37 
 38     private string _poolName;
 39     /// <summary>
 40     /// 链接池名称
 41     /// </summary>
 42     public string PoolName
 43     {
 44         get
 45         {
 46             return Utils.StrIsNullOrEmpty(_poolName) ? "DiscuzNT_MemCache" : _poolName;
 47         }
 48         set
 49         {
 50             _poolName = value;
 51         }
 52     }
 53 
 54     private int _intConnections;
 55     /// <summary>
 56     /// 初始化链接数
 57     /// </summary>
 58     public int IntConnections
 59     {
 60         get
 61         {
 62             return _intConnections > 0 ? _intConnections : 3;
 63         }
 64         set
 65         {
 66             _intConnections = value;
 67         }
 68     }
 69 
 70     private int _minConnections;
 71     /// <summary>
 72     /// 最少链接数
 73     /// </summary>
 74     public int MinConnections
 75     {
 76         get
 77         {
 78             return _minConnections > 0 ? _minConnections : 3;
 79         }
 80         set
 81         {
 82             _minConnections = value;
 83         }
 84     }
 85 
 86     private int _maxConnections;
 87     /// <summary>
 88     /// 最大连接数
 89     /// </summary>
 90     public int MaxConnections
 91     {
 92         get
 93         {
 94             return _maxConnections > 0 ?_maxConnections : 5;
 95         }
 96         set
 97         {
 98             _maxConnections = value;
 99         }
100     }
101 
102     private int _socketConnectTimeout;
103     /// <summary>
104     /// Socket链接超时时间
105     /// </summary>
106     public int SocketConnectTimeout
107     {
108         get
109         {
110             return _socketConnectTimeout > 1000 ? _socketConnectTimeout : 1000;
111         }
112         set
113         {
114             _socketConnectTimeout = value;
115         }
116     }
117 
118     private int _socketTimeout;
119     /// <summary>
120     /// socket超时时间
121     /// </summary>
122     public int SocketTimeout
123     {
124         get
125         {
126             return _socketTimeout > 1000 ? _maintenanceSleep : 3000;
127         }
128         set
129         {
130             _socketTimeout = value;
131         }
132     }
133 
134     private int _maintenanceSleep;
135     /// <summary>
136     /// 维护线程休息时间
137     /// </summary>
138     public int MaintenanceSleep
139     {
140         get
141         {
142             return _maintenanceSleep > 0 ? _maintenanceSleep : 30;
143         }
144         set
145         {
146             _maintenanceSleep = value;
147         }
148     }
149 
150     private bool _failOver;
151     /// <summary>
152     /// 链接失败后是否重启,详情参见[url]http://baike.baidu.com/view/1084309.htm[/url]
153     /// </summary>
154     public bool FailOver
155     {
156         get
157         {
158             return;_failOver;
159         }
160         set
161         {
162             _failOver = value;
163         }
164     }
165 
166     private bool _nagle;
167     /// <summary>
168     /// 是否用nagle算法启动socket
169     /// </summary>
170     public bool Nagle
171     {
172         get
173         {
174             return _nagle;
175         }
176         set
177         {
178             _nagle = value;
179         }
180     }
181 }     

 

      这些参数我们通过注释应该有一些了解,可以说memcached的主要性能都是通过这些参数来决定的,大家应根据自己公司产品和应用的实际情况配置相应的数值。

      当然,做完这一步之后就是对调用“缓存策略”的主体类进行修改来,使其根据对管理后台的设计来决定加载什么样的缓存策略,如下:

 

View Code
 1 /// <summary>
 2 /// Discuz!NT缓存类
 3 /// 对Discuz!NT论坛缓存进行全局控制管理
 4 /// </summary>
 5 public class DNTCache
 6 {
 7     
 8     //通过该变量决定是否启用MemCached
 9     private static bool applyMemCached = MemCachedConfigs.GetConfig().ApplyMemCached;    
10 
11     /// <summary>
12     /// 构造函数
13     /// </summary>
14     private DNTCache()
15     {
16         if (applyMemCached)
17             cs = new MemCachedStrategy();
18         else
19         {
20             cs = new DefaultCacheStrategy();
21 
22             objectXmlMap = rootXml.CreateElement("Cache");
23             //建立内部XML文档.
24             rootXml.AppendChild(objectXmlMap);
25 
26             //LogVisitor clv = new CacheLogVisitor();
27             //cs.Accept(clv);
28 
29             cacheConfigTimer.AutoReset = true;
30             cacheConfigTimer.Enabled = true;
31             cacheConfigTimer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed);
32             cacheConfigTimer.Start();
33      }
34     }

 

     到这里,主要的开发和修改基本上就告一段落了。下面开始介绍一下如果使用Stats命令来查看缓存的分配和使用等情况。之前在枚举类型Stats中看到该命令有几个主要的参数,分别是:

    stats
    stats reset
    stats malloc
    stats maps   
    stats sizes
    stats slabs
    stats items
    stats cachedump slab_id
     limit_num
    stats 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协议远程登陆缓存服务器并输入相应的命令行参数(我记忆力不好,参数多了之后就爱忘)。二是将来会把这个页面功能内置到管理后台上,以便后台管理员可以动态监测每台缓存服务器上的数据。

    
好了,到这里今天的内容就差不多了。在本文中我们看到了使用设计模式的好处,通过它我们可以让自己写的代码支持“变化”。这里不妨再多说几句,大家看到了velocity在使用上也是很方便,如果可以的话,未来可以也会将velocity做成一个“缓存策略”,这样站长或管理员就可以根据自己公司的实际情
况来加以灵活配置了。    

    
      相关资料:   
      memcached 全面剖析.pdf   
      memcached 深度分析    

      Facebook 对memcached的提升

posted on 2012-07-13 17:00  似是而非  阅读(189)  评论(0编辑  收藏  举报