ASP.NET版Memcached监控工具
实现思路
上一篇文章《使用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页面。
程序代码如下:
<%@ Page Language="C#" %>
<%@ Import Namespace="System" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Net.Sockets" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Threading" %>
<%@ Import Namespace="System.Security" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
/*
* 作者:周公
* 日期:2011-03-27
* 原文出处:http://blog.csdn.net/zhoufoxcn 或http://zhoufoxcn.blog.51cto.com/
* 版权说明:本文可以在保留原文出处的情况下使用于非商业用途,周公对此不作任何担保或承诺。
* */
/// <summary>
/// Memcached服务器监控类
/// </summary>
public class MemcachedMonitor
{
/// <summary>
/// 连接Memcached的超时时间
/// </summary>
public TimeSpan ConnectionTimeout { get; set; }
/// <summary>
/// 接收Memcached返回数据的超时时间
/// </summary>
public TimeSpan ReceiveTimeout { get; set; }
private List<IPEndPoint> serverList;
public MemcachedMonitor(ICollection<IPEndPoint> list)
{
ConnectionTimeout = TimeSpan.FromSeconds(10);
ReceiveTimeout = TimeSpan.FromSeconds(20);
serverList = new List<IPEndPoint>();
serverList.AddRange(list);
}
public List<MemcachedServerStats> GetAllServerStats()
{
List<MemcachedServerStats> resultList = new List<MemcachedServerStats>();
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;
}
}
/// <summary>
/// Memcached服务器运行状态数据类,只有当IsReachable为true时获取的数据才有意义,否则表示不可访问或者Memcached挂了
/// </summary>
public class MemcachedServerStats
{
private Dictionary<string, string> results;
/// <summary>
/// 是否可访问,如果不可访问表示网络故障或者Memcached服务器Down掉了
/// </summary>
public bool IsReachable { get; set; }
/// <summary>
/// 服务器运行时间,单位秒(32u)
/// </summary>
public UInt32 Uptime { get; set; }
/// <summary>
/// 服务器当前的UNIX时间(32u)
/// </summary>
public UInt32 Time { get; set; }
/// <summary>
/// 服务器的版本号(string)
/// </summary>
public string Version { get; set; }
/// <summary>
/// 服务器当前存储的内容数量(32u)
/// </summary>
public UInt32 Curr_Items { get; set; }
/// <summary>
/// 服务器启动以来存储过的内容总数(32u)
/// </summary>
public UInt32 Total_Items { get; set; }
/// <summary>
/// 连接数量(32u)
/// </summary>
public UInt32 Curr_Connections { get; set; }
/// <summary>
/// 服务器运行以来接受的连接总数(32u)
/// </summary>
public UInt32 Total_Connections { get; set; }
/// <summary>
/// 服务器分配的连接结构的数量(32u)
/// </summary>
public UInt32 Connection_Structures { get; set; }
/// <summary>
/// 取回请求总数(32u)
/// </summary>
public UInt32 Cmd_Get { get; set; }
/// <summary>
/// 存储请求总数(32u)
/// </summary>
public UInt32 Cmd_Set { get; set; }
/// <summary>
/// 请求成功的总次数(32u)
/// </summary>
public UInt32 Get_Hits { get; set; }
/// <summary>
/// 请求失败的总次数(32u)
/// </summary>
public UInt32 Get_Misses { get; set; }
/// <summary>
/// 服务器当前存储内容所占用的字节数(64u)
/// </summary>
public UInt64 Bytes { get; set; }
/// <summary>
/// 服务器从网络读取到的总字节数(64u)
/// </summary>
public UInt64 Bytes_Read { get; set; }
/// <summary>
/// 服务器向网络发送的总字节数(64u)
/// </summary>
public UInt64 Bytes_Written { get; set; }
/// <summary>
/// 服务器在存储时被允许使用的字节总数(32u)
/// </summary>
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<string, string> results)
{
if (endpoint == null || results == null)
{
throw new ArgumentNullException("point and result can't be null");
}
IPEndPoint = endpoint;
}
public void InitializeData(Dictionary<string, string> 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;
}
}
/// <summary>
/// 与Memcached服务器通讯的Socket封装
/// </summary>
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;
}
/// <summary>
/// 获取Memcached的运行状态
/// </summary>
/// <returns></returns>
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<string, string> serverData = new Dictionary<string, string>(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, "STAT ", 0, 5, StringComparison.Ordinal) != 0)
{
continue;
}
//获取以空格作为分隔符的键值对
string[] parts = result.Remove(0, 5).Split(' ');
if (parts.Length != 2)
{
continue;
}
serverData[parts[0]] = parts[1];
result = ReadLine();
}
stats.InitializeData(serverData);
}
}
catch (Exception exception)
{
stats.IsReachable = false;
//Debug.WriteLine("Exception Message:" + exception.Message);
}
finally
{
}
return stats;
}
/// <summary>
/// 从远程主机的响应流中读取一行数据
/// </summary>
/// <returns></returns>
private string ReadLine()
{
MemoryStream ms = new MemoryStream(50);
bool gotR = false;
byte[] buffer = new byte[1];
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("接收到空响应。");
if (String.Compare(retureValue, GenericErrorResponse, StringComparison.Ordinal) == 0)
throw new NotSupportedException("无效的指令。");
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;
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>ASP.NET版Memcached监控工具</title>
<style>
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;
}
</style>
</head>
<body>
<table border="0">
<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>
<%
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>";
List<IPEndPoint> list = new List<IPEndPoint> { new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11121), MemcachedMonitor.Parse("localhost",11131) };
MemcachedMonitor monitor = new MemcachedMonitor(list);
List<MemcachedServerStats> resultList = monitor.GetAllServerStats();
string result=string.Empty;
foreach (MemcachedServerStats stats in resultList)
{
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);
Response.Write(result);
}
%>
</table>
这些数据所代表的意义如下:
<ul>
<li>pid:32u,服务器进程ID。</li>
<li>uptime:32u, 服务器运行时间,单位秒。</li>
<li>time :32u, 服务器当前的UNIX时间。</li>
<li>version :string, 服务器的版本号。 </li>
<li>curr_items :32u, 服务器当前存储的内容数量</li>
<li>total_items :32u, 服务器启动以来存储过的内容总数。</li>
<li>bytes :64u, 服务器当前存储内容所占用的字节数。</li>
<li>curr_connections :32u, 连接数量。 </li>
<li>total_connections :32u, 服务器运行以来接受的连接总数。</li>
<li>connection_structures:32u, 服务器分配的连接结构的数量。</li>
<li>cmd_get :32u, 取回请求总数。 </li>
<li>cmd_set :32u, 存储请求总数。 </li>
<li>get_hits :32u, 请求成功的总次数。</li>
<li>get_misses :32u, 请求失败的总次数。</li>
<li>bytes_read :64u, 服务器从网络读取到的总字节数。</li>
<li>bytes_written :64u, 服务器向网络发送的总字节数。</li>
<li>limit_maxbytes :32u, 服务器在存储时被允许使用的字节总数。</li>
</ul>
上面的描述中32u和64u表示32位和64位无符号整数,string表示是string类型数据。<br />
作者博客:<a href="http://blog.csdn.net/zhoufoxcn" target="_blank">CSDN博客</a>|<a href="http://zhoufoxcn.blog.51cto.com/" target="_blank">51CTO博客</a>
</body>
</html>
说明:周公对CSS不太熟悉,所以没有好好设计页面的显示效果,以能显示各Memcached的运行状态为准。
总结:Memcached作为一个非常不错的分布式缓存确实能很大程度上提高程序的性能。在上面的例子中有关检测Memcached运行状态数据的代码可以提取出来应用于WinForm或者Windows Service,一旦检测出Memcached不可访问可以采取更灵活的方式,比如发送邮件到指定的邮箱,关于这一部分的功能相信大家都能轻易实现,所以在这里就不再赘述了。
周公
2011-03-28