【转】系统缓存全解析二:动态缓存(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):
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 { set; get; }
71 }
上面类实现的接口Discuz.Cache.ICacheStrategy定义如下:
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一些常操作和相关初始化实例调用的“封装”,下面是是其变量定义和初始化构造方法的代码:
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命令来获取当前缓存服务器上的数据信息和内存分配信息等,相应的方法如下(详情见注释):
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):
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的主要性能都是通过这些参数来决定的,大家应根据自己公司产品和应用的实际情况配置相应的数值。
当然,做完这一步之后就是对调用“缓存策略”的主体类进行修改来,使其根据对管理后台的设计来决定加载什么样的缓存策略,如下:
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的提升