在上一篇文章《使用Memcached提高.NET应用程序的性能》中周公讲述如何在.NET中使用Memcached来提高.NET应用程序的性 能。在实际的使用中有可能出现Memcached因为某些不可预知的原因挂掉,一旦出现这样的情况,就会再次给数据库增加巨大的压力,因此需要监控 Memcached的运行情况。周公在网上找过,在网上有PHP版的Memcached监控工具,打开那个PHP页面就可以看到各个Memcached的 运行情况,一旦不能获取到这些数据,说明Memcached不可访问,不可访问的原因可能是因为网络故障或者Memcached挂掉了,虽然原因不同,但 是结果是一样的。参照了Enyim Memcached和PHP版Memcached监控工具的实现,周公实现了一个.NET版的监控工具。
实现思路
上一篇文章《使用Memcached提高.NET应用程序的性能》中周公讲述了可以通过Telnet来获取Memcached的运行状况,通 过"stats"命令得到Memcached的数据,如果得不到相应的数据就证明Memcached不可访问。
其中向Memcached发送"stats"命令得到的数据的意义如下:
pid:32u,服务器进程ID。
uptime:32u, 服务器运行时间,单位秒。
time :32u, 服务器当前的UNIX时间。
version :string, 服务器的版本号。
curr_items :32u, 服务器当前存储的内容数量 Current number of items stored by the server
total_items :32u, 服务器启动以来存储过的内容总数。
bytes :64u, 服务器当前存储内容所占用的字节数。
curr_connections :32u, 连接数量。
total_connections :32u, 服务器运行以来接受的连接总数。
connection_structures:32u, 服务器分配的连接结构的数量。
cmd_get :32u, 取回请求总数。
cmd_set :32u, 存储请求总数。
get_hits :32u, 请求成功的总次数。
get_misses :32u, 请求失败的总次数。
bytes_read :64u, 服务器从网络读取到的总字节数。
bytes_written :64u, 服务器向网络发送的总字节数。
limit_maxbytes :32u, 服务器在存储时被允许使用的字节总数。
上面的描述中32u和64u表示32位和64位无符号整数,string表示是string类型数据。
在本篇中我们通过Socket而不是Telnet连接到Memcached,然后解析返回的数据。
程序代码
为了便于管理和维护,在本示例中使用了单页模式,也就是所有的代码都在一个ASPX页面中,没有对应的aspx.cs页面。
程序代码如下:

  1. <%@ Page Language="C#" %>
  2. <%@ Import Namespace="System" %>
  3. <%@ Import Namespace="System.IO" %>
  4. <%@ Import Namespace="System.Net" %>
  5. <%@ Import Namespace="System.Net.Sockets" %>
  6. <%@ Import Namespace="System.Collections.Generic" %>
  7. <%@ Import Namespace="System.Threading" %>
  8. <%@ Import Namespace="System.Security" %>
  9. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  10. <script runat="server">
  11. /*
  12. * 作者:周公
  13. * 日期:2011-03-27
  14. * 原文出处:http://blog.csdn.net/zhoufoxcn 或http://zhoufoxcn.blog.51cto.com
  15. * 版权说明:本文可以在保留原文出处的情况下使用于非商业用途,周公对此不作任何担保或承诺。
  16. * */
  17. /// <summary>
  18. /// Memcached服务器监控类
  19. /// </summary>
  20. public class MemcachedMonitor
  21. {
  22. /// <summary>
  23. /// 连接Memcached的超时时间
  24. /// </summary>
  25. public TimeSpan ConnectionTimeout { get; set; }
  26. /// <summary>
  27. /// 接收Memcached返回数据的超时时间
  28. /// </summary>
  29. public TimeSpan ReceiveTimeout { get; set; }
  30. private List<IPEndPoint> serverList;
  31. public MemcachedMonitor(ICollection<IPEndPoint> list)
  32. {
  33. ConnectionTimeout = TimeSpan.FromSeconds(10);
  34. ReceiveTimeout = TimeSpan.FromSeconds(20);
  35. serverList = new List<IPEndPoint>();
  36. serverList.AddRange(list);
  37. }
  38. public List<MemcachedServerStats> GetAllServerStats()
  39. {
  40. List<MemcachedServerStats> resultList = new List<MemcachedServerStats>();
  41. foreach (IPEndPoint endPoint in serverList)
  42. {
  43. resultList.Add(GetServerStats(endPoint, ConnectionTimeout, ReceiveTimeout));
  44. }
  45. return resultList;
  46. }
  47. public static MemcachedServerStats GetServerStats(IPEndPoint ip, TimeSpan connectionTimeout, TimeSpan receiveTimeout)
  48. {
  49. MemcachedSocket socket = new MemcachedSocket(ip, connectionTimeout, receiveTimeout);
  50. MemcachedServerStats stats = socket.GetStats();
  51. return stats;
  52. }
  53. public static IPEndPoint Parse(string hostName,int port)
  54. {
  55. IPHostEntry host=Dns.GetHostEntry(hostName);
  56. IPEndPoint endPoint = null;
  57. foreach (IPAddress ip in host.AddressList)
  58. {
  59. if (ip.AddressFamily == AddressFamily.InterNetwork)
  60. {
  61. endPoint = new IPEndPoint(ip, port);
  62. break;
  63. }
  64. }
  65. return endPoint;
  66. }
  67. }
  68. /// <summary>
  69. /// Memcached服务器运行状态数据类,只有当IsReachable为true时获取的数据才有意义,否则表示不可访问或者Memcached挂了
  70. /// </summary>
  71. public class MemcachedServerStats
  72. {
  73. private Dictionary<string, string> results;
  74. /// <summary>
  75. /// 是否可访问,如果不可访问表示网络故障或者Memcached服务器Down掉了
  76. /// </summary>
  77. public bool IsReachable { get; set; }
  78. /// <summary>
  79. /// 服务器运行时间,单位秒(32u)
  80. /// </summary>
  81. public UInt32 Uptime { get; set; }
  82. /// <summary>
  83. /// 服务器当前的UNIX时间(32u)
  84. /// </summary>
  85. public UInt32 Time { get; set; }
  86. /// <summary>
  87. /// 服务器的版本号(string)
  88. /// </summary>
  89. public string Version { get; set; }
  90. /// <summary>
  91. /// 服务器当前存储的内容数量(32u)
  92. /// </summary>
  93. public UInt32 Curr_Items { get; set; }
  94. /// <summary>
  95. /// 服务器启动以来存储过的内容总数(32u)
  96. /// </summary>
  97. public UInt32 Total_Items { get; set; }
  98. /// <summary>
  99. /// 连接数量(32u)
  100. /// </summary>
  101. public UInt32 Curr_Connections { get; set; }
  102. /// <summary>
  103. /// 服务器运行以来接受的连接总数(32u)
  104. /// </summary>
  105. public UInt32 Total_Connections { get; set; }
  106. /// <summary>
  107. /// 服务器分配的连接结构的数量(32u)
  108. /// </summary>
  109. public UInt32 Connection_Structures { get; set; }
  110. /// <summary>
  111. /// 取回请求总数(32u)
  112. /// </summary>
  113. public UInt32 Cmd_Get { get; set; }
  114. /// <summary>
  115. /// 存储请求总数(32u)
  116. /// </summary>
  117. public UInt32 Cmd_Set { get; set; }
  118. /// <summary>
  119. /// 请求成功的总次数(32u)
  120. /// </summary>
  121. public UInt32 Get_Hits { get; set; }
  122. /// <summary>
  123. /// 请求失败的总次数(32u)
  124. /// </summary>
  125. public UInt32 Get_Misses { get; set; }
  126. /// <summary>
  127. /// 服务器当前存储内容所占用的字节数(64u)
  128. /// </summary>
  129. public UInt64 Bytes { get; set; }
  130. /// <summary>
  131. /// 服务器从网络读取到的总字节数(64u)
  132. /// </summary>
  133. public UInt64 Bytes_Read { get; set; }
  134. /// <summary>
  135. /// 服务器向网络发送的总字节数(64u)
  136. /// </summary>
  137. public UInt64 Bytes_Written { get; set; }
  138. /// <summary>
  139. /// 服务器在存储时被允许使用的字节总数(32u)
  140. /// </summary>
  141. public UInt32 Limit_Maxbytes { get; set; }
  142. public IPEndPoint IPEndPoint { get; set; }
  143. public MemcachedServerStats(IPEndPoint endpoint)
  144. {
  145. if (endpoint == null)
  146. {
  147. throw new ArgumentNullException("endpoint can't be null");
  148. }
  149. IPEndPoint = endpoint;
  150. }
  151. public MemcachedServerStats(IPEndPoint endpoint, Dictionary<string, string> results)
  152. {
  153. if (endpoint == null || results == null)
  154. {
  155. throw new ArgumentNullException("point and result can't be null");
  156. }
  157. IPEndPoint = endpoint;
  158. }
  159. public void InitializeData(Dictionary<string, string> results)
  160. {
  161. if (results == null)
  162. {
  163. throw new ArgumentNullException("result can't be null");
  164. }
  165. this.results = results;
  166. Uptime = GetUInt32("uptime");
  167. Time = GetUInt32("time");
  168. Version = GetRaw("version");
  169. Curr_Items = GetUInt32("curr_items");
  170. Total_Items = GetUInt32("total_items");
  171. Curr_Connections = GetUInt32("curr_connections");
  172. Total_Connections = GetUInt32("total_connections");
  173. Connection_Structures = GetUInt32("connection_structures");
  174. Cmd_Get = GetUInt32("cmd_get");
  175. Cmd_Set = GetUInt32("cmd_set");
  176. Get_Hits = GetUInt32("get_hits");
  177. Get_Misses = GetUInt32("get_misses");
  178. Bytes = GetUInt64("bytes");
  179. Bytes_Read = GetUInt64("bytes_read");
  180. Bytes_Written = GetUInt64("bytes_written");
  181. Limit_Maxbytes = GetUInt32("limit_maxbytes");
  182. }
  183. private string GetRaw(string key)
  184. {
  185. string value = string.Empty;
  186. results.TryGetValue(key, out value);
  187. return value;
  188. }
  189. private UInt32 GetUInt32(string key)
  190. {
  191. string value = GetRaw(key);
  192. UInt32 uptime;
  193. UInt32.TryParse(value, out uptime);
  194. return uptime;
  195. }
  196. private UInt64 GetUInt64(string key)
  197. {
  198. string value = GetRaw(key);
  199. UInt64 uptime;
  200. UInt64.TryParse(value, out uptime);
  201. return uptime;
  202. }
  203. }
  204. /// <summary>
  205. /// 与Memcached服务器通讯的Socket封装
  206. /// </summary>
  207. internal class MemcachedSocket : IDisposable
  208. {
  209. private const string CommandString = "stats\r\n";//发送查询Memcached状态的指令,以"\r\n"作为命令的结束
  210. private const int ErrorResponseLength = 13;
  211. private const string GenericErrorResponse = "ERROR";
  212. private const string ClientErrorResponse = "CLIENT_ERROR ";
  213. private const string ServerErrorResponse = "SERVER_ERROR ";
  214. private Socket socket;
  215. private IPEndPoint endpoint;
  216. private BufferedStream bufferedStream;
  217. private NetworkStream networkStream;
  218. public MemcachedSocket(IPEndPoint ip, TimeSpan connectionTimeout, TimeSpan receiveTimeout)
  219. {
  220. if (ip == null)
  221. {
  222. throw new ArgumentNullException("ip", "不能为空!");
  223. }
  224. endpoint = ip;
  225. socket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  226. socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, connectionTimeout == TimeSpan.MaxValue ? Timeout.Infinite : (int)connectionTimeout.TotalMilliseconds);
  227. socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, receiveTimeout == TimeSpan.MaxValue ? Timeout.Infinite : (int)receiveTimeout.TotalMilliseconds);
  228. // all operations are "atomic", we do not send small chunks of data
  229. socket.NoDelay = true;
  230. }
  231. /// <summary>
  232. /// 获取Memcached的运行状态
  233. /// </summary>
  234. /// <returns></returns>
  235. public MemcachedServerStats GetStats()
  236. {
  237. MemcachedServerStats stats = new MemcachedServerStats(endpoint);
  238. try
  239. {
  240. socket.Connect(endpoint);
  241. networkStream = new NetworkStream(socket);
  242. bufferedStream = new BufferedStream(networkStream);
  243. byte[] buffer = Encoding.ASCII.GetBytes(CommandString);
  244. SocketError socketError;
  245. socket.Send(buffer, 0, buffer.Length, SocketFlags.None, out socketError);
  246. if (socketError != SocketError.Success)
  247. {
  248. stats.IsReachable = false;
  249. }
  250. else
  251. {
  252. stats.IsReachable = true;
  253. string result = ReadLine();
  254. Dictionary<string, string> serverData = new Dictionary<string, string>(StringComparer.Ordinal);
  255. while (!string.IsNullOrEmpty(result))
  256. {
  257. // 返回的数据信息以"END"作为结束标记
  258. if (String.Compare(result, "END", StringComparison.Ordinal) == 0)
  259. break;
  260. //期望的响应格式是:"STAT 名称 值"(注意"STAT 名称 值"之间有空格)
  261. if (result.Length < 6 || String.Compare(result, 0, "STAT ", 0, 5, StringComparison.Ordinal) != 0)
  262. {
  263. continue;
  264. }
  265. //获取以空格作为分隔符的键值对
  266. string[] parts = result.Remove(0, 5).Split(' ');
  267. if (parts.Length != 2)
  268. {
  269. continue;
  270. }
  271. serverData[parts[0]] = parts[1];
  272. result = ReadLine();
  273. }
  274. stats.InitializeData(serverData);
  275. }
  276. }
  277. catch (Exception exception)
  278. {
  279. stats.IsReachable = false;
  280. //Debug.WriteLine("Exception Message:" + exception.Message);
  281. }
  282. finally
  283. {
  284. }
  285. return stats;
  286. }
  287. /// <summary>
  288. /// 从远程主机的响应流中读取一行数据
  289. /// </summary>
  290. /// <returns></returns>
  291. private string ReadLine()
  292. {
  293. MemoryStream ms = new MemoryStream(50);
  294. bool gotR = false;
  295. byte[] buffer = new byte[1];
  296. int data;
  297. try
  298. {
  299. while (true)
  300. {
  301. data = bufferedStream.ReadByte();
  302. if (data == 13)
  303. {
  304. gotR = true;
  305. continue;
  306. }
  307. if (gotR)
  308. {
  309. if (data == 10)
  310. break;
  311. ms.WriteByte(13);
  312. gotR = false;
  313. }
  314. ms.WriteByte((byte)data);
  315. }
  316. }
  317. catch (IOException)
  318. {
  319. throw;
  320. }
  321. string retureValue = Encoding.ASCII.GetString(ms.GetBuffer(), 0, (int)ms.Length);
  322. if (String.IsNullOrEmpty(retureValue))
  323. throw new Exception("接收到空响应。");
  324. if (String.Compare(retureValue, GenericErrorResponse, StringComparison.Ordinal) == 0)
  325. throw new NotSupportedException("无效的指令。");
  326. if (retureValue.Length >= ErrorResponseLength)
  327. {
  328. if (String.Compare(retureValue, 0, ClientErrorResponse, 0, ErrorResponseLength, StringComparison.Ordinal) == 0)
  329. {
  330. throw new Exception(retureValue.Remove(0, ErrorResponseLength));
  331. }
  332. else if (String.Compare(retureValue, 0, ServerErrorResponse, 0, ErrorResponseLength, StringComparison.Ordinal) == 0)
  333. {
  334. throw new Exception(retureValue.Remove(0, ErrorResponseLength));
  335. }
  336. }
  337. return retureValue;
  338. }
  339. public void Dispose()
  340. {
  341. if (socket != null)
  342. {
  343. socket.Shutdown(SocketShutdown.Both);
  344. }
  345. socket = null;
  346. networkStream.Dispose();
  347. networkStream = null;
  348. bufferedStream.Dispose();
  349. bufferedStream = null;
  350. }
  351. }
  352. </script>
  353. <html xmlns="http://www.w3.org/1999/xhtml">
  354. <head>
  355. <title>ASP.NET版Memcached监控工具</title>
  356. <style>
  357. a {
  358. color:#000000;
  359. text-decoration:none;
  360. }
  361. a.current {
  362. color:#0000FF;
  363. }
  364. a:hover {
  365. text-decoration: none;
  366. }
  367. body {
  368. font-family: verdana, geneva,tahoma, helvetica, arial, sans-serif;
  369. font-size: 100%;
  370. background-color:#FFFFFF;
  371. margin: 0em;
  372. }
  373. ul {
  374. font-size:80%;
  375. color:#666666;
  376. line-height: 1.5em;
  377. list-style: none;
  378. }
  379. </style>
  380. </head>
  381. <body>
  382. <table border="0">
  383. <tr><th>IP</th><th>Version</th><th>IsReachable</th><th>Bytes</th><th>Bytes_Read</th><th>Bytes_Written</th><th>Cmd_Get</th><th>Cmd_Set</th><th>Curr_Connections</th><th>Curr_Items</th><th>Get_Hits</th><th>Get_Misses</th><th>Limit_Maxbytes</th><th>Total_Items</th></tr>
  384. <%
  385. String format = "<tr><td>{0}</td><td>{1}</th><th>{2}</th><th>{3}</th><th>{4}</th><th>{5}</th><th>{6}</th><th>{7}</th><th>{8}</th><th>{9}</th><th>{10}</th><th>{11}</th><th>{12}</th><th>{13}</th></tr>";
  386. List<IPEndPoint> list = new List<IPEndPoint> { new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11121), MemcachedMonitor.Parse("localhost",11131) };
  387. MemcachedMonitor monitor = new MemcachedMonitor(list);
  388. List<MemcachedServerStats> resultList = monitor.GetAllServerStats();
  389. string result=string.Empty;
  390. foreach (MemcachedServerStats stats in resultList)
  391. {
  392. result = string.Format(format, stats.IPEndPoint, stats.Version,stats.IsReachable, stats.Bytes, stats.Bytes_Read, stats.Bytes_Written, stats.Cmd_Get, stats.Cmd_Set, stats.Curr_Connections, stats.Curr_Items, stats.Get_Hits, stats.Get_Misses, stats.Limit_Maxbytes, stats.Total_Items);
  393. Response.Write(result);
  394. }
  395. %>
  396. </table>
  397. 这些数据所代表的意义如下:
  398. <ul>
  399. <li>pid:32u,服务器进程ID。</li>
  400. <li>uptime:32u, 服务器运行时间,单位秒。</li>
  401. <li>time :32u, 服务器当前的UNIX时间。</li>
  402. <li>version :string, 服务器的版本号。 </li>
  403. <li>curr_items :32u, 服务器当前存储的内容数量</li>
  404. <li>total_items :32u, 服务器启动以来存储过的内容总数。</li>
  405. <li>bytes :64u, 服务器当前存储内容所占用的字节数。</li>
  406. <li>curr_connections :32u, 连接数量。 </li>
  407. <li>total_connections :32u, 服务器运行以来接受的连接总数。</li>
  408. <li>connection_structures:32u, 服务器分配的连接结构的数量。</li>
  409. <li>cmd_get :32u, 取回请求总数。 </li>
  410. <li>cmd_set :32u, 存储请求总数。 </li>
  411. <li>get_hits :32u, 请求成功的总次数。</li>
  412. <li>get_misses :32u, 请求失败的总次数。</li>
  413. <li>bytes_read :64u, 服务器从网络读取到的总字节数。</li>
  414. <li>bytes_written :64u, 服务器向网络发送的总字节数。</li>
  415. <li>limit_maxbytes :32u, 服务器在存储时被允许使用的字节总数。</li>
  416. </ul>
  417. 上面的描述中32u和64u表示32位和64位无符号整数,string表示是string类型数据。<br />
  418. 作者博客:<a href="http://blog.csdn.net/zhoufoxcn" target="_blank">CSDN博客</a>|<a href="http://zhoufoxcn.blog.51cto.com" target="_blank">51CTO博客</a>
  419. </body>
  420. </html>
 

说明:周公对CSS不太熟悉,所以没有好好设计页面的显示效果,以能显示各Memcached的运行状态为准。
总结:Memcached作为一个非常不错的分布式缓存确实能很大程度上提高程序的性能。在上面的例子中有关检测Memcached运行状态数据的代码可 以提取出来应用于WinForm或者Windows Service,一旦检测出Memcached不可访问可以采取更灵活的方式,比如发送邮件到指定的邮箱,关于这一部分的功能相信大家都能轻易实现,所以 在这里就不再赘述了。

05-08 08:04