ASP.NET版Memcached监控工具

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

你可能感兴趣的:(memcached)