Memcached分布式缓存策略不是由服务器端至支持的,多台服务器之间并不知道彼此的存在。分布式的实现是由客户端代码(Memcached.ClientLibrary)通过缓存key-server映射来实现的,基本原理就是对缓存key求hash值,用hash值对服务器数量进行模运算,该key值被分配到模运算结果为索引的那台server上。

Memcached.ClientLibrary对缓存key计算hashcode的核心算法如下:

 /// <summary>
/// Returns appropriate SockIO object given
/// string cache key and optional hashcode.
///
/// Trys to get SockIO from pool. Fails over
/// to additional pools in event of server failure.
/// </summary>
/// <param name="key">hashcode for cache key</param>
/// <param name="hashCode">if not null, then the int hashcode to use</param>
/// <returns>SockIO obj connected to server</returns>
public SockIO GetSock(string key, object hashCode)
{
string hashCodeString = "<null>";
if(hashCode != null)
hashCodeString = hashCode.ToString(); if(Log.IsDebugEnabled)
{
Log.Debug(GetLocalizedString("cache socket pick").Replace("$$Key$$", key).Replace("$$HashCode$$", hashCodeString));
} if (key == null || key.Length == )
{
if(Log.IsDebugEnabled)
{
Log.Debug(GetLocalizedString("null key"));
}
return null;
} if(!_initialized)
{
if(Log.IsErrorEnabled)
{
Log.Error(GetLocalizedString("get socket from uninitialized pool"));
}
return null;
} // if no servers return null
if(_buckets.Count == )
return null; // if only one server, return it
if(_buckets.Count == )
return GetConnection((string)_buckets[]); int tries = ; // generate hashcode
int hv;
if(hashCode != null)
{
hv = (int)hashCode;
}
else
{ // NATIVE_HASH = 0
// OLD_COMPAT_HASH = 1
// NEW_COMPAT_HASH = 2
switch(_hashingAlgorithm)
{
case HashingAlgorithm.Native:
hv = key.GetHashCode();
break; case HashingAlgorithm.OldCompatibleHash:
hv = OriginalHashingAlgorithm(key);
break; case HashingAlgorithm.NewCompatibleHash:
hv = NewHashingAlgorithm(key);
break; default:
// use the native hash as a default
hv = key.GetHashCode();
_hashingAlgorithm = HashingAlgorithm.Native;
break;
}
} // keep trying different servers until we find one
while(tries++ <= _buckets.Count)
{
// get bucket using hashcode
// get one from factory
int bucket = hv % _buckets.Count;
if(bucket < )
bucket += _buckets.Count; SockIO sock = GetConnection((string)_buckets[bucket]); if(Log.IsDebugEnabled)
{
Log.Debug(GetLocalizedString("cache choose").Replace("$$Bucket$$", _buckets[bucket].ToString()).Replace("$$Key$$", key));
} if(sock != null)
return sock; // if we do not want to failover, then bail here
if(!_failover)
return null; // if we failed to get a socket from this server
// then we try again by adding an incrementer to the
// current key and then rehashing
switch(_hashingAlgorithm)
{
case HashingAlgorithm.Native:
hv += ((string)("" + tries + key)).GetHashCode();
break; case HashingAlgorithm.OldCompatibleHash:
hv += OriginalHashingAlgorithm("" + tries + key);
break; case HashingAlgorithm.NewCompatibleHash:
hv += NewHashingAlgorithm("" + tries + key);
break; default:
// use the native hash as a default
hv += ((string)("" + tries + key)).GetHashCode();
_hashingAlgorithm = HashingAlgorithm.Native;
break;
}
} return null;
}

根据缓存key得到服务器的核心代码

从源码中(62--82行代码)可以发现,计算hashcode的算法共三种:

(1)HashingAlgorithm.Native: 即使用.NET本身的hash算法,速度快,但与其他client可能不兼容,例如需要和java、ruby的client共享缓存的情况;

(2)HashingAlgorithm.OldCompatibleHash: 可以与其他客户端兼容,但速度慢;

(3)HashingAlgorithm.NewCompatibleHash: 可以与其他客户端兼容,据称速度快。

进一步分析发现,Memcached.ClientLibrary默认计算缓存key的hashcode的方式就是HashingAlgorithm.Native,而HashingAlgorithm.Native计算hashcode的算法为“hv = key.GetHashCode()”,即用了.net类库string类型自带的GetHashCode()方法。

Bug就要浮现出来了,根据微软(http://msdn.microsoft.com/zh-cn/library/system.object.gethashcode.aspx)对GetHashCode的解释:the .NET Framework does not guarantee the default implementation of the GetHashCode method, and the value this method returns may differ between .NET Framework versions and platforms, such as 32-bit and 64-bit platforms。string类型的GetHashCode()函数并不能保证不同平台同一个字符串返回的hash值相同,这样问题就出来了,对于不同服务器的同一缓存key来说,产生的hashcode可能不同,同一key对应的数据可能缓存到了不同的MemCache服务器上,数据的一致性无法保证,清除缓存的代码也可能失效。

// 64位 4.0
[__DynamicallyInvokable, ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), SecuritySafeCritical]
public unsafe override int GetHashCode()
{
if (HashHelpers.s_UseRandomizedStringHashing)
{
return string.InternalMarvin32HashString(this, this.Length, 0L);
}
IntPtr arg_25_0;
IntPtr expr_1C = arg_25_0 = this;
if (expr_1C != )
{
arg_25_0 = (IntPtr)((int)expr_1C + RuntimeHelpers.OffsetToStringData);
}
char* ptr = arg_25_0;
int num = ;
int num2 = num;
char* ptr2 = ptr;
int num3;
while ((num3 = (int)(*(ushort*)ptr2)) != )
{
num = ((num << ) + num ^ num3);
num3 = (int)(*(ushort*)(ptr2 + (IntPtr) / ));
if (num3 == )
{
break;
}
num2 = ((num2 << ) + num2 ^ num3);
ptr2 += (IntPtr) / ;
}
return num + num2 * ;
} // 64位 2.0
// string
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public unsafe override int GetHashCode()
{
IntPtr arg_0F_0;
IntPtr expr_06 = arg_0F_0 = this;
if (expr_06 != )
{
arg_0F_0 = (IntPtr)((int)expr_06 + RuntimeHelpers.OffsetToStringData);
}
char* ptr = arg_0F_0;
int num = ;
int num2 = num;
char* ptr2 = ptr;
int num3;
while ((num3 = (int)(*(ushort*)ptr2)) != )
{
num = ((num << ) + num ^ num3);
num3 = (int)(*(ushort*)(ptr2 + (IntPtr) / ));
if (num3 == )
{
break;
}
num2 = ((num2 << ) + num2 ^ num3);
ptr2 += (IntPtr) / ;
}
return num + num2 * ;
} //32位 4.0
[__DynamicallyInvokable, ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), SecuritySafeCritical]
public unsafe override int GetHashCode()
{
if (HashHelpers.s_UseRandomizedStringHashing)
{
return string.InternalMarvin32HashString(this, this.Length, 0L);
}
IntPtr arg_25_0;
IntPtr expr_1C = arg_25_0 = this;
if (expr_1C != )
{
arg_25_0 = (IntPtr)((int)expr_1C + RuntimeHelpers.OffsetToStringData);
}
char* ptr = arg_25_0;
int num = ;
int num2 = num;
int* ptr2 = (int*)ptr;
int i;
for (i = this.Length; i > ; i -= )
{
num = ((num << ) + num + (num >> ) ^ *ptr2);
num2 = ((num2 << ) + num2 + (num2 >> ) ^ ptr2[(IntPtr) / ]);
ptr2 += (IntPtr) / ;
}
if (i > )
{
num = ((num << ) + num + (num >> ) ^ *ptr2);
}
return num + num2 * ;
}

GetHashCode几种版本的实现代码

解决问题的方法就是不要用MemCache默认的hash算法,实现方式有两种:

(1)初始化MemCache服务器的时候,指定为MemCahce自带其它的hash算法,代码为“this.pool.HashingAlgorithm = HashingAlgorithm.OldCompatibleHash;”。

(2)自定义hash算法,调用set()、get()、delete()等方式时传递hash值,这几个方法有参数传递hashcode的重载。

参考资料:分析Memcached客户端如何把缓存数据分布到多个服务器上(转)memcached client - memcacheddotnet (Memcached.ClientLibrary) 1.1.5memcache分布式实现Object.GetHashCode 方法关于 HashCode做key的可能性

MemCache分布式缓存的一个bug的更多相关文章

  1. C# Memcache分布式缓存简单入门

    什么是Memcache?能做什么? 以下是百度的观点: memcache是一套分布式的高速缓存系统,由LiveJournal的Brad Fitzpatrick开发,但目前被许多网站使用以提升网站的访问 ...

  2. memcache 分布式缓存

    转载地址:http://www.cnblogs.com/phpstudy2015-6/p/6713164.html 作者:那一叶随风 1.memcached分布式简介 memcached虽然称为“分布 ...

  3. CYQ.Data 对于分布式缓存Redis、MemCache高可用的改进及性能测试

    背景: 随着.NET Core 在 Linux 下的热动,相信动不动就要分布式或集群的应用的需求,会慢慢火起来. 所以这段时间一直在研究和思考分布式集群的问题,同时也在思考把几个框架的思维相对提升到这 ...

  4. Memcached 分布式缓存系统部署与调试

    Memcache 分布式缓存系统部署与调试 工作机制:通过在内存中开辟一块区域来维持一个大的hash表来加快页面访问速度,和数据库是独立的;目前主要用来缓存数据库的数据;存放在内存的数据通过LRU算法 ...

  5. Golang校招简历项目-简单的分布式缓存

    前言 前段时间,校招投了golang岗位,但是没什么好的项目往简历上写,于是参考了许多网上资料,做了一个简单的分布式缓存项目. 现在闲下来了,打算整理下. github项目地址:https://git ...

  6. 分布式缓存 memcache学习

    1.使用分布式缓存是为了解决多台机器共享信息的问题,通过访问一个ip和端口来可以访问不同的IIS服务器 2.memcache基础原理 在Socket服务器端存储数据是以键值对的形式存储 内存处理的算法 ...

  7. MemCache分布式内存对象缓存系统

    MemCache超详细解读 MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而 ...

  8. 分布式缓存Memcached/memcached/memcache详解及区别

    先来解释下标题中的三种写法:首字母大写的Memcached,指的是Memcached服务器,就是独立运行Memcached的后台服务器,用于存储缓存数据的“容器”.memcached和memcache ...

  9. Nginx+Memcache+一致性hash算法 实现页面分布式缓存(转)

    网站响应速度优化包括集群架构中很多方面的瓶颈因素,这里所说的将页面静态化.实现分布式高速缓存就是其中的一个很好的解决方案... 1)先来看看Nginx负载均衡 Nginx负载均衡依赖自带的 ngx_h ...

随机推荐

  1. windows 录音程序(一)

    (一)概述 1.依赖条件:winmm.lib 2.步骤: (1)打开设备 -----  waveInOpen(打开一个音频输入设备): (2)开始录音 ----- waveInStart开始录音: ( ...

  2. day10---multiprocess 多进程

    multiprocess Queue  \ Pipe 只是实现进程间数据的传递 Manager 实现了进程间数据的共享,即多个进程可以修改同一份数据   进程模块 multiprocessing #! ...

  3. 丹佛机场行李系统Postmortem

    丹佛机场行李系统做Postmortem总结 事情起因是因为丹佛市场承诺进行机场建设,因此此项目问题不可回避,必须完成,合作的双方都是富有经验的公司但是最后依然变成了不可控的项目,经过小组讨论后我们认为 ...

  4. sqlserver 读取xml 字符串方法

    declare @xml xml declare @propertyName varchar(50)  declare @str nvarchar(max)   set @propertyName = ...

  5. android wireshark抓包和fiddler抓包

    一 wireshark 1.把电脑的网络做为热点 2.开启wifi热点后,被测手机连接到该热点: 3.启动wireshark,选择做为热点的网卡,点击start开始抓包: 4.操作手机,可以抓取到手机 ...

  6. mongoDB研究笔记:分片集群部署

    前面几篇文章的分析复制集解决了数据库的备份与自动故障转移,但是围绕数据库的业务中当前还有两个方面的问题变得越来越重要.一是海量数据如何存储?二是如何高效的读写海量数据?尽管复制集也可以实现读写分析,如 ...

  7. Android性能优化典范第一季

    2015年伊始,Google发布了关于Android性能优化典范的专题,一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App.课程专题不仅仅介绍了Android系统中有关 ...

  8. 高性能网站架构设计之缓存篇(2)- Redis C#客户端

    在上一篇中我简单的介绍了如何利用redis自带的客户端连接server并执行命令来操作它,但是如何在我们做的项目或产品中操作这个强大的内存数据库呢?首先我们来了解一下redis的原理吧. 官方文档上是 ...

  9. OSX 下搭建Asp.Net vNext的开发环境

    开年第一天,按照惯例逛逛各个网站,看看7天有没有什么错过的东西,偶见VS 2015的CPT 6发布了,据说更新ASP.NET,就顺便去官方网站看了看,也忘记在什么地方偶然发现一个叫OmniSharp的 ...

  10. SQLServer性能优化之 nolock,大幅提升数据库查询性能

    公司数据库随着时间的增长,数据越来越多,查询速度也越来越慢.进数据库看了一下,几十万调的数据,查询起来确实很费时间. 要提升SQL的查询效能,一般来说大家会以建立索引(index)为第一考虑.其实除了 ...