1. Key & Hashcode
当有多台 memcached Server 存在时,memcached client library 依据以下规则来选择目标服务器。
(1) 如果只有一台服务器,则直接返回。
(2) 如果 hashcode == null,则使用 key.GetHashCode,有多种 HashingAlgorithm 可供选择。
(3) 通过计算 hashcode % servers.count 来决定使用哪一台服务器。
(4) 如果该服务器连接失败,则调整相关参数进行循环,直到获取可用服务器。
伪代码
SockIO GetServer(key, hashcode)
{
if (servers.count == 1) return GetConnection(servers[0]);
int hv = hashcode != null ? hashcode : key.GetHashcode();
int tries = 0;
while (++tires <= servers.count)
{
int index = hv % servers.count;
if (index < 0) index += servers.count;
SockIO sock = GetConnection(servers[index]);
if (sock != null)
return sock;
else
hv = Modify(hv, key);
}
}
我们可以通过 SockIOPool.HashingAlgorithm 属性设置不同的哈希算法。
public enum HashingAlgorithm
{
///<summary>
/// native String.hashCode() - fast (cached) but not compatible with other clients
///</summary>
Native = 0,
///<summary>
/// original compatibility hashing alg (works with other clients)
///</summary>
OldCompatibleHash = 1,
///<summary>
/// new CRC32 based compatibility hashing algorithm (fast and works with other clients)
///</summary>
NewCompatibleHash = 2
}
如果有必要的话,我们可以用下面这样的代码获知 (key, hashcode) 会被存储到哪台服务器上。
var sock = SockIOPool.GetInstance().GetSock("a");
Console.WriteLine(sock.Host);
sock.Close();
获取对象的时候,也是用同样的算法首先决定从哪台server上获取数据。 相关细节可参考 SockIOPool.cs 文件 "public SockIO GetSock(string key, object hashCode)" 方法。在 MemcachedClient 中,所有的方法都会通过 "SockIOPool.GetInstance(poolName).GetSock(key, hashCode);" 来和服务器进行交互,每种操作都提供了是否显式提供 hashcode 参数的方法重载。
public bool Delete(string key);
public bool Delete(string key, object hashCode, DateTime expiry);
... ...
必要的时候,我们可以显式指定 hashcode。
MemcachedClient mc = new MemcachedClient();
string key = "cacheKey1";
object value = SomeClass.getObject();
int hash = 45;
mc.set(key, value, hash);
2. Serializable
MemcachedClient 使用二进制序列化引擎(BinaryFormatter)将要存储的对象转换为 byte 数组,因此非基元类型需要添加可序列化特性。
[Serializable]
class MyClass
{
public int I { get; set; }
}
var mc = new MemcachedClient();
mc.Set("a", new MyClass { I = 1234});
var o = mc.Get("a") as MyClass;
Console.WriteLine(o.I);
相关细节可参考 MemcachedClient.cs "private bool Set(string cmdname, string key, object obj, DateTime expiry, object hashCode, bool asString)" 方法。
3. Compression
MemcachedClient 通过调用 ICSharpCode.SharpZipLib.GZipOutputStream 对数据进行压缩处理。EnableCompression 属性就是压缩是否启用的开关,同时 CompressionThreshold 属性提供一个阀值,只有长度大于该阀值的数据才会被压缩 (默认 15KB)。
我们做一个试验。
var mc = new MemcachedClient { EnableCompression = false };
for (int i = 0; i < 300; i++)
{
mc.Set(i.ToString(), new byte[1024 * 500]);
}
Console.WriteLine(mc.KeyExists("0"));
// ---------------
mc.FlushAll();
// ---------------
mc.EnableCompression = true;
for (int i = 0; i < 300; i++)
{
mc.Set(i.ToString(), new byte[1024 * 500]);
}
Console.WriteLine(mc.KeyExists("0"));
当我们不使用压缩时,每次传输 500KB 数据,很快 memcached Server 就达到内存上限,最先存入的数据会被移除。而启用压缩后,我们可以存储更多的数据。
相关细节可参考 MemcachedClient.cs "private bool Set(string cmdname, string key, object obj, DateTime expiry, object hashCode, bool asString)" 方法。
5. Expiry
MemcachedClient 允许我们在操作数据时设定失效时间。我们做个小实验看看效果。
Console.WriteLine(DateTime.Now);
Console.WriteLine("---------------");
var mc = new MemcachedClient();
mc.Set("a", 1234, DateTime.Now.AddSeconds(10));
for (int i = 0; i < 20; i++)
{
Thread.Sleep(1000);
Console.WriteLine("{0} - {1}", DateTime.Now, mc.KeyExists("a"));
}
虽然这个实验的计时 不太准确,但输出结果基本验证了失效时间的设定。
4. SockIO
我们可以使用 SockIO 直接操作协议,通过发送相关命令进行数据读写操作。
演示: 获取指定服务器的版本信息。
var sock = SockIOPool.GetInstance().GetConnection("192.168.1.101:1234");
try
{
sock.Write(Encoding.UTF8.GetBytes("versionrn"));
sock.Flush();
Console.WriteLine(sock.ReadLine());
}
catch (IOException ioe)
{
Console.WriteLine(ioe);
}
finally
{
sock.Close();
}