tijian_tieying/web/cyqdata-master/Cache/Redis/RedisClient.cs
2025-02-20 12:14:39 +08:00

571 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Text;
namespace CYQ.Data.Cache
{
/// <summary>
/// Memcached client main class.
/// Use the static methods Setup and GetInstance to setup and get an instance of the client for use.
/// </summary>
internal class RedisClient
{
#region Static fields and methods.
private static Dictionary<string, RedisClient> instances = new Dictionary<string, RedisClient>();
private static LogAdapter logger = LogAdapter.GetLogger(typeof(RedisClient));
/// <summary>
/// Static method for creating an instance. This method will throw an exception if the name already exists.
/// </summary>
/// <param name="name">The name of the instance.</param>
/// <param name="servers">A list of memcached servers in standard notation: host:port.
/// If port is omitted, the default value of 11211 is used.
/// Both IP addresses and host names are accepted, for example:
/// "localhost", "127.0.0.1", "cache01.example.com:12345", "127.0.0.1:12345", etc.</param>
public static RedisClient Setup(string name, string[] servers)
{
if (instances.ContainsKey(name))
{
Error.Throw("Trying to configure RedisClient instance \"" + name + "\" twice.");
}
RedisClient client = new RedisClient(name, servers);
instances[name] = client;
return client;
}
/// <summary>
/// Static method which checks if a given named RedisClient instance exists.
/// </summary>
/// <param name="name">The name of the instance.</param>
/// <returns></returns>
public static bool Exists(string name)
{
return instances.ContainsKey(name);
}
/// <summary>
/// Static method for getting the default instance named "default".
/// </summary>
private static RedisClient defaultInstance = null;
public static RedisClient GetInstance()
{
return defaultInstance ?? (defaultInstance = GetInstance("default"));
}
/// <summary>
/// Static method for getting an instance.
/// This method will first check for named instances that has been set up programmatically.
/// If no such instance exists, it will check the "beitmemcached" section of the standard
/// config file and see if it can find configuration info for it there.
/// If that also fails, an exception is thrown.
/// </summary>
/// <param name="name">The name of the instance.</param>
/// <returns>The named instance.</returns>
public static RedisClient GetInstance(string name)
{
RedisClient c;
if (instances.TryGetValue(name, out c))
{
return c;
}
Error.Throw("Unable to find RedisClient instance \"" + name + "\".");
return null;
}
#endregion
#region Fields, constructors, and private methods.
public readonly string Name;
internal readonly ServerPool serverPool;
/// <summary>
/// If you specify a key prefix, it will be appended to all keys before they are sent to the memcached server.
/// They key prefix is not used when calculating which server a key belongs to.
/// </summary>
public string KeyPrefix { get { return keyPrefix; } set { keyPrefix = value; } }
private string keyPrefix = "";
/// <summary>
/// The send receive timeout is used to determine how long the client should wait for data to be sent
/// and received from the server, specified in milliseconds. The default value is 2000.
/// </summary>
public int SendReceiveTimeout { get { return serverPool.SendReceiveTimeout; } set { serverPool.SendReceiveTimeout = value; } }
/// <summary>
/// The connect timeout is used to determine how long the client should wait for a connection to be established,
/// specified in milliseconds. The default value is 2000.
/// </summary>
public int ConnectTimeout { get { return serverPool.ConnectTimeout; } set { serverPool.ConnectTimeout = value; } }
/// <summary>
/// The min pool size determines the number of sockets the socket pool will keep.
/// Note that no sockets will be created on startup, only on use, so the socket pool will only
/// contain this amount of sockets if the amount of simultaneous requests goes above it.
/// The default value is 5.
/// </summary>
public uint MinPoolSize
{
get { return serverPool.MinPoolSize; }
set
{
if (value > MaxPoolSize) { Error.Throw("MinPoolSize (" + value + ") may not be larger than the MaxPoolSize (" + MaxPoolSize + ")."); }
serverPool.MinPoolSize = value;
}
}
/// <summary>
/// The max pool size determines how large the socket connection pool is allowed to grow.
/// There can be more sockets in use than this amount, but when the extra sockets are returned, they will be destroyed.
/// The default value is 10.
/// </summary>
public uint MaxPoolSize
{
get { return serverPool.MaxPoolSize; }
set
{
if (value < MinPoolSize) { Error.Throw("MaxPoolSize (" + value + ") may not be smaller than the MinPoolSize (" + MinPoolSize + ")."); }
serverPool.MaxPoolSize = value;
}
}
/// <summary>
/// If the pool contains more than the minimum amount of sockets, and a socket is returned that is older than this recycle age
/// that socket will be destroyed instead of put back in the pool. This allows the pool to shrink back to the min pool size after a peak in usage.
/// The default value is 30 minutes.
/// </summary>
public TimeSpan SocketRecycleAge { get { return serverPool.SocketRecycleAge; } set { serverPool.SocketRecycleAge = value; } }
/// <summary>
/// ָ<><D6B8><EFBFBD><EFBFBD><EFBFBD>ݳ<EFBFBD><DDB3>ȳ<EFBFBD><C8B3><EFBFBD>ֵʱ<D6B5><CAB1><EFBFBD><EFBFBD>ѹ<EFBFBD><D1B9>
/// </summary>
private uint compressionThreshold = 1024 * 128; //128kb
/// <summary>
/// If an object being stored is larger in bytes than the compression threshold, it will internally be compressed before begin stored,
/// and it will transparently be decompressed when retrieved. Only strings, byte arrays and objects can be compressed.
/// The default value is 1048576 bytes = 1MB.
/// </summary>
public uint CompressionThreshold { get { return compressionThreshold; } set { compressionThreshold = value; } }
//Private constructor
private RedisClient(string name, string[] hosts)
{
if (String.IsNullOrEmpty(name))
{
Error.Throw("Name of RedisClient instance cannot be empty.");
}
if (hosts == null || hosts.Length == 0)
{
Error.Throw("Cannot configure RedisClient with empty list of hosts.");
}
Name = name;
serverPool = new ServerPool(hosts, CacheType.Redis);
serverPool.OnAuthEvent += new ServerPool.AuthDelegate(serverPool_OnAuthEvent);
}
bool serverPool_OnAuthEvent(MSocket socket)
{
if (!Auth(socket.SocketPool.password, socket))
{
Error.Throw("Auth password fail!");
}
return true;
}
/// <summary>
/// Private key hashing method that uses the modified FNV hash.
/// </summary>
/// <param name="key">The key to hash.</param>
/// <returns>The hashed key.</returns>
private uint hash(string key)
{
checkKey(key);
return BitConverter.ToUInt32(new ModifiedFNV1_32().ComputeHash(Encoding.UTF8.GetBytes(key)), 0);
}
/// <summary>
/// Private hashing method for user-supplied hash values.
/// </summary>
/// <param name="hashvalue">The user-supplied hash value to hash.</param>
/// <returns>The hashed value</returns>
private uint hash(uint hashvalue)
{
return BitConverter.ToUInt32(new ModifiedFNV1_32().ComputeHash(BitConverter.GetBytes(hashvalue)), 0);
}
/// <summary>
/// Private multi-hashing method.
/// </summary>
/// <param name="keys">An array of keys to hash.</param>
/// <returns>An arrays of hashes.</returns>
private uint[] hash(string[] keys)
{
uint[] result = new uint[keys.Length];
for (int i = 0; i < keys.Length; i++)
{
result[i] = hash(keys[i]);
}
return result;
}
/// <summary>
/// Private multi-hashing method for user-supplied hash values.
/// </summary>
/// <param name="hashvalues">An array of keys to hash.</param>
/// <returns>An arrays of hashes.</returns>
private uint[] hash(uint[] hashvalues)
{
uint[] result = new uint[hashvalues.Length];
for (int i = 0; i < hashvalues.Length; i++)
{
result[i] = hash(hashvalues[i]);
}
return result;
}
/// <summary>
/// Private key-checking method.
/// Throws an exception if the key does not conform to memcached protocol requirements:
/// It may not contain whitespace, it may not be null or empty, and it may not be longer than 250 characters.
/// </summary>
/// <param name="key">The key to check.</param>
private void checkKey(string key)
{
if (key == null)
{
throw new ArgumentNullException("Key may not be null.");
}
if (key.Length == 0)
{
throw new ArgumentException("Key may not be empty.");
}
if (key.Length > 250)
{
throw new ArgumentException("Key may not be longer than 250 characters.");
}
foreach (char c in key)
{
if (c <= 32)
{
throw new ArgumentException("Key may not contain whitespace or control characters.");
}
}
}
//Private Unix-time converter
private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static int getUnixTime(DateTime datetime)
{
return (int)(datetime.ToUniversalTime() - epoch).TotalSeconds;
}
#endregion
#region Set<EFBFBD><EFBFBD>Append
public void Append(string key, object value, int seconds) { Set("append", key, false, value, hash(key), seconds); }
public void Set(string key, object value, int seconds) { Set("set", key, false, value, hash(key), seconds); }
private bool Set(string command, string key, bool keyIsChecked, object value, uint hash, int expirySeconds)
{
if (!keyIsChecked)
{
checkKey(key);
}
string result = serverPool.Execute<string>(hash, "", delegate(MSocket socket)
{
SerializedType type;
byte[] bytes;
byte[] typeBit = new byte[1];
try
{
bytes = Serializer.Serialize(value, out type, CompressionThreshold);
typeBit[0] = (byte)type;
}
catch (Exception e)
{
logger.Error("Error serializing object for key '" + key + "'.", e);
return "";
}
CheckDB(socket, hash);
using (RedisCommand cmd = new RedisCommand(socket, 3, command))
{
cmd.WriteKey(keyPrefix + key);
cmd.WriteValue(typeBit, bytes);
result = socket.ReadResponse();
if (result[0] != '-')
{
if (expirySeconds > 0)
{
cmd.Reset(3, "EXPIRE");
cmd.WriteKey(keyPrefix + key);
cmd.WriteValue(expirySeconds.ToString());
result = socket.ReadResponse();
}
}
return result;
}
});
return !string.IsNullOrEmpty(result);
}
#endregion
#region Get
public object Get(string key) { return Get("get", key, true, hash(key)); }
private object Get(string command, string key, bool keyIsChecked, uint hash)
{
if (!keyIsChecked)
{
checkKey(key);
}
object value = serverPool.Execute<object>(hash, null, delegate(MSocket socket)
{
CheckDB(socket, hash);
using (RedisCommand cmd = new RedisCommand(socket, 2, command))
{
cmd.WriteKey(keyPrefix + key);
}
string result = socket.ReadResponse();
if (!string.IsNullOrEmpty(result) && result[0] == '$')
{
int len = 0;
if (int.TryParse(result.Substring(1), out len) && len > 0)
{
byte[] bytes = socket.ReadBytes(len);
if (bytes.Length > 0)
{
byte[] data = new byte[bytes.Length - 1];
SerializedType st = (SerializedType)bytes[0];
Array.Copy(bytes, 1, data, 0, data.Length);
bytes = null;
return Serializer.DeSerialize(data, st);
}
}
}
return null;
});
return value;
}
#endregion
#region Exists
public bool ContainsKey(string key) { return ContainsKey(key, true, hash(key)); }
private bool ContainsKey(string key, bool keyIsChecked, uint hash)
{
if (!keyIsChecked)
{
checkKey(key);
}
return serverPool.Execute<bool>(hash, false, delegate(MSocket socket)
{
CheckDB(socket, hash);
using (RedisCommand cmd = new RedisCommand(socket, 2, "Exists"))
{
cmd.WriteKey(keyPrefix + key);
}
string result = socket.ReadResponse();
return !result.StartsWith(":0") && !result.StartsWith("-");
});
}
#endregion
#region Select DB
internal void CheckDB(MSocket socket, uint hash)
{
if (AppConfig.Cache.RedisUseDBCount > 1 || AppConfig.Cache.RedisUseDBIndex > 0)
{
uint db = AppConfig.Cache.RedisUseDBIndex > 0 ? (uint)AppConfig.Cache.RedisUseDBIndex : (hash % (uint)AppConfig.Cache.RedisUseDBCount);//Ĭ<>Ϸ<EFBFBD>ɢ<EFBFBD><C9A2>16<31><36>DB<44>С<EFBFBD>
if (socket.DB != db)
{
socket.DB = db;
using (RedisCommand cmd = new RedisCommand(socket, 2, "Select"))
{
cmd.WriteKey(db.ToString());
}
socket.SkipToEndOfLine();
}
}
}
//public bool SelectDB(SocketPool pool, int num)
//{
// return serverPool.Execute<bool>(pool, false, delegate(MSocket socket)
// {
// using (RedisCommand cmd = new RedisCommand(socket, 2, "Select"))
// {
// cmd.WriteKey(num.ToString());
// }
// return !socket.ReadResponse().StartsWith("-");
// });
//}
#endregion
#region Delete
public bool Delete(string key) { return Delete(key, true, hash(key), 0); }
private bool Delete(string key, bool keyIsChecked, uint hash, int time)
{
if (!keyIsChecked)
{
checkKey(key);
}
return serverPool.Execute<bool>(hash, false, delegate(MSocket socket)
{
CheckDB(socket, hash);
using (RedisCommand cmd = new RedisCommand(socket, 2, "DEL"))
{
cmd.WriteKey(keyPrefix + key);
}
return socket.ReadResponse().StartsWith(":1");
});
}
#endregion
#region Delete
private bool Auth(string password, MSocket socket)
{
if (!string.IsNullOrEmpty(password))
{
using (RedisCommand cmd = new RedisCommand(socket, 2, "AUTH"))
{
cmd.WriteKey(password);
}
return socket.ReadResponse().StartsWith("+OK");
}
return true;
}
#endregion
#region Flush All
/// <summary>
/// This method corresponds to the "flush_all" command in the memcached protocol.
/// When this method is called, it will send the flush command to all servers, thereby deleting
/// all items on all servers.
/// Use the overloads to set a delay for the flushing. If the parameter staggered is set to true,
/// the client will increase the delay for each server, i.e. the first will flush after delay*0,
/// the second after delay*1, the third after delay*2, etc. If set to false, all servers will flush
/// after the same delay.
/// It returns true if the command was successful on all servers.
/// </summary>
public bool FlushAll()
{
foreach (SocketPool pool in serverPool.HostList)
{
serverPool.Execute(pool, delegate(MSocket socket)
{
using (RedisCommand cmd = new RedisCommand(socket, 1, "flushall"))
{
}
});
}
return true;
}
#endregion
#region Stats
/// <summary>
/// This method corresponds to the "stats" command in the memcached protocol.
/// It will send the stats command to all servers, and it will return a Dictionary for each server
/// containing the results of the command.
/// </summary>
public Dictionary<string, Dictionary<string, string>> Stats()
{
Dictionary<string, Dictionary<string, string>> results = new Dictionary<string, Dictionary<string, string>>();
foreach (SocketPool pool in serverPool.HostList)
{
results.Add(pool.Host, stats(pool));
}
return results;
}
private Dictionary<string, string> stats(SocketPool pool)
{
if (pool == null)
{
return null;
}
Dictionary<string, string> dic = new Dictionary<string, string>();
serverPool.Execute(pool, delegate(MSocket socket)
{
using (RedisCommand cmd = new RedisCommand(socket, 1, "info"))
{
}
string result = socket.ReadResponse();
if (!string.IsNullOrEmpty(result) && result[0] == '$')
{
string line = null;
while (true)
{
line = socket.ReadLine();
if (line == null)
{
break;
}
string[] s = line.Split(':');
dic.Add(s[0], s[1]);
}
}
});
return dic;
}
#endregion
#region Status
internal int okServer = 0, errorServer = 0;
/// <summary>
/// This method retrives the status from the serverpool. It checks the connection to all servers
/// and returns usage statistics for each server.
/// </summary>
public Dictionary<string, Dictionary<string, string>> Status()
{
okServer = 0;
errorServer = 0;
Dictionary<string, Dictionary<string, string>> results = new Dictionary<string, Dictionary<string, string>>();
foreach (SocketPool pool in serverPool.HostList)
{
Dictionary<string, string> result = new Dictionary<string, string>();
if (serverPool.Execute<bool>(pool, false, delegate { return true; }))
{
okServer++;
result.Add("Status", "Ok");
}
else
{
errorServer++;
result.Add("Status", "Dead, next retry at: " + pool.DeadEndPointRetryTime);
}
result.Add("Sockets in pool", pool.Poolsize.ToString());
result.Add("Acquired sockets", pool.Acquired.ToString());
result.Add("Sockets reused", pool.ReusedSockets.ToString());
result.Add("New sockets created", pool.NewSockets.ToString());
result.Add("New sockets failed", pool.FailedNewSockets.ToString());
result.Add("Sockets died in pool", pool.DeadSocketsInPool.ToString());
result.Add("Sockets died on return", pool.DeadSocketsOnReturn.ToString());
result.Add("Dirty sockets on return", pool.DirtySocketsOnReturn.ToString());
results.Add(pool.Host, result);
}
return results;
}
#endregion
}
}