xuol001 发表于 2018-12-24 11:59:42

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页面。
程序代码如下:



[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]/*
[*] * 作者:周公
[*] * 日期:2011-03-27
[*] * 原文出处:http://blog.csdn.net/zhoufoxcn 或http://zhoufoxcn.blog.运维网.com
[*] * 版权说明:本文可以在保留原文出处的情况下使用于非商业用途,周公对此不作任何担保或承诺。
[*] * */
[*]      
[*]    ///
[*]    /// Memcached服务器监控类
[*]    ///
[*]    public class MemcachedMonitor
[*]    {
[*]      ///
[*]      /// 连接Memcached的超时时间
[*]      ///
[*]      public TimeSpan ConnectionTimeout { get; set; }
[*]      ///
[*]      /// 接收Memcached返回数据的超时时间
[*]      ///
[*]      public TimeSpan ReceiveTimeout { get; set; }
[*]      private List serverList;
[*]      public MemcachedMonitor(ICollection list)
[*]      {
[*]            ConnectionTimeout = TimeSpan.FromSeconds(10);
[*]            ReceiveTimeout = TimeSpan.FromSeconds(20);
[*]            serverList = new List();
[*]            serverList.AddRange(list);
[*]      }
[*]
[*]      public List GetAllServerStats()
[*]      {
[*]            List resultList = new List();
[*]            foreach (IPEndPoint endPoint in serverList)
[*]            {
[*]                resultList.Add(GetServerStats(endPoint, ConnectionTimeout, ReceiveTimeout));
[*]            }
[*]            return resultList;
[*]      }
[*]
[*]      public static MemcachedServerStats GetServerStats(IPEndPoint ip, TimeSpan connectionTimeout, TimeSpan receiveTimeout)
[*]      {
[*]            MemcachedSocket socket = new MemcachedSocket(ip, connectionTimeout, receiveTimeout);
[*]            MemcachedServerStats stats = socket.GetStats();
[*]            return stats;
[*]      }
[*]
[*]      public static IPEndPoint Parse(string hostName,int port)
[*]      {
[*]            IPHostEntry host=Dns.GetHostEntry(hostName);
[*]            IPEndPoint endPoint = null;
[*]            foreach (IPAddress ip in host.AddressList)
[*]            {
[*]                if (ip.AddressFamily == AddressFamily.InterNetwork)
[*]                {
[*]                  endPoint = new IPEndPoint(ip, port);
[*]                  break;
[*]                }
[*]            }
[*]            return endPoint;
[*]      }
[*]    }
[*]    ///
[*]    /// Memcached服务器运行状态数据类,只有当IsReachable为true时获取的数据才有意义,否则表示不可访问或者Memcached挂了
[*]    ///
[*]    public class MemcachedServerStats
[*]    {
[*]      private Dictionary results;
[*]      ///
[*]      /// 是否可访问,如果不可访问表示网络故障或者Memcached服务器Down掉了
[*]      ///
[*]      public bool IsReachable { get; set; }
[*]      ///
[*]      /// 服务器运行时间,单位秒(32u)
[*]      ///
[*]      public UInt32 Uptime { get; set; }
[*]      ///
[*]      /// 服务器当前的UNIX时间(32u)
[*]      ///
[*]      public UInt32 Time { get; set; }
[*]      ///
[*]      /// 服务器的版本号(string)
[*]      ///
[*]      public string Version { get; set; }
[*]      ///
[*]      /// 服务器当前存储的内容数量(32u)
[*]      ///
[*]      public UInt32 Curr_Items { get; set; }
[*]      ///
[*]      /// 服务器启动以来存储过的内容总数(32u)
[*]      ///
[*]      public UInt32 Total_Items { get; set; }
[*]      ///
[*]      /// 连接数量(32u)
[*]      ///
[*]      public UInt32 Curr_Connections { get; set; }
[*]      ///
[*]      /// 服务器运行以来接受的连接总数(32u)
[*]      ///
[*]      public UInt32 Total_Connections { get; set; }
[*]      ///
[*]      /// 服务器分配的连接结构的数量(32u)
[*]      ///
[*]      public UInt32 Connection_Structures { get; set; }
[*]      ///
[*]      /// 取回请求总数(32u)
[*]      ///
[*]      public UInt32 Cmd_Get { get; set; }
[*]      ///
[*]      /// 存储请求总数(32u)
[*]      ///
[*]      public UInt32 Cmd_Set { get; set; }
[*]      ///
[*]      /// 请求成功的总次数(32u)
[*]      ///
[*]      public UInt32 Get_Hits { get; set; }
[*]      ///
[*]      /// 请求失败的总次数(32u)
[*]      ///
[*]      public UInt32 Get_Misses { get; set; }
[*]      ///
[*]      /// 服务器当前存储内容所占用的字节数(64u)
[*]      ///
[*]      public UInt64 Bytes { get; set; }
[*]      ///
[*]      /// 服务器从网络读取到的总字节数(64u)
[*]      ///
[*]      public UInt64 Bytes_Read { get; set; }
[*]      ///
[*]      /// 服务器向网络发送的总字节数(64u)
[*]      ///
[*]      public UInt64 Bytes_Written { get; set; }
[*]      ///
[*]      /// 服务器在存储时被允许使用的字节总数(32u)
[*]      ///
[*]      public UInt32 Limit_Maxbytes { get; set; }
[*]      public IPEndPoint IPEndPoint { get; set; }
[*]
[*]      public MemcachedServerStats(IPEndPoint endpoint)
[*]      {
[*]            if (endpoint == null)
[*]            {
[*]                throw new ArgumentNullException("endpoint can't be null");
[*]            }
[*]            IPEndPoint = endpoint;
[*]      }
[*]
[*]      public MemcachedServerStats(IPEndPoint endpoint, Dictionary results)
[*]      {
[*]            if (endpoint == null || results == null)
[*]            {
[*]                throw new ArgumentNullException("point and result can't be null");
[*]            }
[*]            IPEndPoint = endpoint;
[*]
[*]      }
[*]
[*]      public void InitializeData(Dictionary results)
[*]      {
[*]            if (results == null)
[*]            {
[*]                throw new ArgumentNullException("result can't be null");
[*]            }
[*]            this.results = results;
[*]            Uptime = GetUInt32("uptime");
[*]            Time = GetUInt32("time");
[*]            Version = GetRaw("version");
[*]            Curr_Items = GetUInt32("curr_items");
[*]            Total_Items = GetUInt32("total_items");
[*]            Curr_Connections = GetUInt32("curr_connections");
[*]            Total_Connections = GetUInt32("total_connections");
[*]            Connection_Structures = GetUInt32("connection_structures");
[*]            Cmd_Get = GetUInt32("cmd_get");
[*]            Cmd_Set = GetUInt32("cmd_set");
[*]            Get_Hits = GetUInt32("get_hits");
[*]            Get_Misses = GetUInt32("get_misses");
[*]            Bytes = GetUInt64("bytes");
[*]            Bytes_Read = GetUInt64("bytes_read");
[*]            Bytes_Written = GetUInt64("bytes_written");
[*]            Limit_Maxbytes = GetUInt32("limit_maxbytes");
[*]      }
[*]
[*]      private string GetRaw(string key)
[*]      {
[*]            string value = string.Empty;
[*]            results.TryGetValue(key, out value);
[*]            return value;
[*]      }
[*]
[*]      private UInt32 GetUInt32(string key)
[*]      {
[*]            string value = GetRaw(key);
[*]            UInt32 uptime;
[*]            UInt32.TryParse(value, out uptime);
[*]            return uptime;
[*]      }
[*]
[*]      private UInt64 GetUInt64(string key)
[*]      {
[*]            string value = GetRaw(key);
[*]            UInt64 uptime;
[*]            UInt64.TryParse(value, out uptime);
[*]            return uptime;
[*]      }
[*]    }
[*]    ///
[*]    /// 与Memcached服务器通讯的Socket封装
[*]    ///
[*]    internal class MemcachedSocket : IDisposable
[*]    {
[*]      private const string CommandString = "stats\r\n";//发送查询Memcached状态的指令,以"\r\n"作为命令的结束
[*]      private const int ErrorResponseLength = 13;
[*]      private const string GenericErrorResponse = "ERROR";
[*]      private const string ClientErrorResponse = "CLIENT_ERROR ";
[*]      private const string ServerErrorResponse = "SERVER_ERROR ";
[*]      private Socket socket;
[*]      private IPEndPoint endpoint;
[*]      private BufferedStream bufferedStream;
[*]      private NetworkStream networkStream;
[*]
[*]      public MemcachedSocket(IPEndPoint ip, TimeSpan connectionTimeout, TimeSpan receiveTimeout)
[*]      {
[*]            if (ip == null)
[*]            {
[*]                throw new ArgumentNullException("ip", "不能为空!");
[*]            }
[*]            endpoint = ip;
[*]
[*]            socket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
[*]
[*]            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, connectionTimeout == TimeSpan.MaxValue ? Timeout.Infinite : (int)connectionTimeout.TotalMilliseconds);
[*]            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, receiveTimeout == TimeSpan.MaxValue ? Timeout.Infinite : (int)receiveTimeout.TotalMilliseconds);
[*]
[*]            // all operations are "atomic", we do not send small chunks of data
[*]            socket.NoDelay = true;
[*]      }
[*]      ///
[*]      /// 获取Memcached的运行状态
[*]      ///
[*]      ///
[*]      public MemcachedServerStats GetStats()
[*]      {
[*]            MemcachedServerStats stats = new MemcachedServerStats(endpoint);
[*]            try
[*]            {
[*]                socket.Connect(endpoint);
[*]                networkStream = new NetworkStream(socket);
[*]                bufferedStream = new BufferedStream(networkStream);
[*]                byte[] buffer = Encoding.ASCII.GetBytes(CommandString);
[*]
[*]                SocketError socketError;
[*]                socket.Send(buffer, 0, buffer.Length, SocketFlags.None, out socketError);
[*]                if (socketError != SocketError.Success)
[*]                {
[*]                  stats.IsReachable = false;
[*]                }
[*]                else
[*]                {
[*]                  stats.IsReachable = true;
[*]                  string result = ReadLine();
[*]                  Dictionary serverData = new Dictionary(StringComparer.Ordinal);
[*]                  while (!string.IsNullOrEmpty(result))
[*]                  {
[*]                        // 返回的数据信息以"END"作为结束标记
[*]                        if (String.Compare(result, "END", StringComparison.Ordinal) == 0)
[*]                            break;
[*]
[*]                        //期望的响应格式是:"STAT 名称 值"(注意"STAT 名称 值"之间有空格)
[*]                        if (result.Length < 6 || String.Compare(result, 0, &quot;STAT &quot;, 0, 5, StringComparison.Ordinal) != 0)
[*]                        {
[*]                            continue;
[*]                        }
[*]
[*]                        //获取以空格作为分隔符的键值对
[*]                        string[] parts = result.Remove(0, 5).Split(' ');
[*]                        if (parts.Length != 2)
[*]                        {
[*]                            continue;
[*]                        }
[*]                        serverData] = parts;
[*]                        result = ReadLine();
[*]                  }
[*]                  stats.InitializeData(serverData);
[*]                }
[*]            }
[*]            catch (Exception exception)
[*]            {
[*]                stats.IsReachable = false;
[*]                //Debug.WriteLine(&quot;Exception Message:&quot; + exception.Message);
[*]            }
[*]            finally
[*]            {
[*]            }
[*]            return stats;
[*]
[*]      }
[*]      ///
[*]      /// 从远程主机的响应流中读取一行数据
[*]      ///
[*]      ///
[*]      private string ReadLine()
[*]      {
[*]            MemoryStream ms = new MemoryStream(50);
[*]
[*]            bool gotR = false;
[*]            byte[] buffer = new byte;
[*]            int data;
[*]
[*]            try
[*]            {
[*]                while (true)
[*]                {
[*]                  data = bufferedStream.ReadByte();
[*]
[*]                  if (data == 13)
[*]                  {
[*]                        gotR = true;
[*]                        continue;
[*]                  }
[*]
[*]                  if (gotR)
[*]                  {
[*]                        if (data == 10)
[*]                            break;
[*]
[*]                        ms.WriteByte(13);
[*]
[*]                        gotR = false;
[*]                  }
[*]
[*]                  ms.WriteByte((byte)data);
[*]                }
[*]            }
[*]            catch (IOException)
[*]            {
[*]
[*]                throw;
[*]            }
[*]
[*]            string retureValue = Encoding.ASCII.GetString(ms.GetBuffer(), 0, (int)ms.Length);
[*]
[*]
[*]            if (String.IsNullOrEmpty(retureValue))
[*]                throw new Exception(&quot;接收到空响应。&quot;);
[*]
[*]            if (String.Compare(retureValue, GenericErrorResponse, StringComparison.Ordinal) == 0)
[*]                throw new NotSupportedException(&quot;无效的指令。&quot;);
[*]
[*]            if (retureValue.Length >= ErrorResponseLength)
[*]            {
[*]                if (String.Compare(retureValue, 0, ClientErrorResponse, 0, ErrorResponseLength, StringComparison.Ordinal) == 0)
[*]                {
[*]                  throw new Exception(retureValue.Remove(0, ErrorResponseLength));
[*]                }
[*]                else if (String.Compare(retureValue, 0, ServerErrorResponse, 0, ErrorResponseLength, StringComparison.Ordinal) == 0)
[*]                {
[*]                  throw new Exception(retureValue.Remove(0, ErrorResponseLength));
[*]                }
[*]            }
[*]
[*]            return retureValue;
[*]      }
[*]
[*]      public void Dispose()
[*]      {
[*]            if (socket != null)
[*]            {
[*]                socket.Shutdown(SocketShutdown.Both);
[*]            }
[*]            socket = null;
[*]            networkStream.Dispose();
[*]            networkStream = null;
[*]            bufferedStream.Dispose();
[*]            bufferedStream = null;
[*]      }
[*]    }
[*]
[*]
[*]
[*]
[*]    ASP.NET版Memcached监控工具
[*]   
[*]      a {
[*]    color:#000000;
[*]    text-decoration:none;
[*]}
[*]a.current {
[*]    color:#0000FF;
[*]}
[*]a:hover {
[*]    text-decoration: none;
[*]}
[*]body {
[*]    font-family: verdana, geneva,tahoma, helvetica, arial, sans-serif;
[*]    font-size: 100%;
[*]    background-color:#FFFFFF;
[*]    margin: 0em;
[*]}
[*]ul {
[*]    font-size:80%;
[*]    color:#666666;
[*]    line-height: 1.5em;
[*]    list-style: none;
[*]}
[*]   
[*]
[*]
[*]
[*]IPVersionIsReachableBytesBytes_ReadBytes_WrittenCmd_GetCmd_SetCurr_ConnectionsCurr_ItemsGet_HitsGet_MissesLimit_MaxbytesTotal_Items
[*]
[*]
[*]这些数据所代表的意义如下:
[*]
[*]pid:32u,服务器进程ID。   
[*]uptime:32u, 服务器运行时间,单位秒。   
[*]time :32u, 服务器当前的UNIX时间。
[*]version :string, 服务器的版本号。
[*]curr_items :32u, 服务器当前存储的内容数量   
[*]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类型数据。
[*]作者博客:CSDN博客|运维网博客
[*]
[*]


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



页: [1]
查看完整版本: ASP.NET版Memcached监控工具