设为首页 收藏本站
查看: 1601|回复: 0

[经验分享] MemCache分布式缓存的一个bug

[复制链接]

尚未签到

发表于 2015-8-31 07:45:46 | 显示全部楼层 |阅读模式
  Memcached分布式缓存策略不是由服务器端至支持的,多台服务器之间并不知道彼此的存在。分布式的实现是由客户端代码(Memcached.ClientLibrary)通过缓存key-server映射来实现的,基本原理就是对缓存key求hash值,用hash值对服务器数量进行模运算,该key值被分配到模运算结果为索引的那台server上。
  Memcached.ClientLibrary对缓存key计算hashcode的核心算法如下:


DSC0000.gif DSC0001.gif


  1 /// <summary>
  2 /// Returns appropriate SockIO object given
  3 /// string cache key and optional hashcode.
  4 ///
  5 /// Trys to get SockIO from pool.  Fails over
  6 /// to additional pools in event of server failure.
  7 /// </summary>
  8 /// <param name="key">hashcode for cache key</param>
  9 /// <param name="hashCode">if not null, then the int hashcode to use</param>
10 /// <returns>SockIO obj connected to server</returns>
11 public SockIO GetSock(string key, object hashCode)
12 {
13     string hashCodeString = "<null>";
14     if(hashCode != null)
15         hashCodeString = hashCode.ToString();
16
17     if(Log.IsDebugEnabled)
18     {
19         Log.Debug(GetLocalizedString("cache socket pick").Replace("$$Key$$", key).Replace("$$HashCode$$", hashCodeString));
20     }
21
22     if (key == null || key.Length == 0)
23     {
24         if(Log.IsDebugEnabled)
25         {
26             Log.Debug(GetLocalizedString("null key"));
27         }
28         return null;
29     }
30
31     if(!_initialized)
32     {
33         if(Log.IsErrorEnabled)
34         {
35             Log.Error(GetLocalizedString("get socket from uninitialized pool"));
36         }
37         return null;
38     }
39
40     // if no servers return null
41     if(_buckets.Count == 0)
42         return null;
43
44     // if only one server, return it
45     if(_buckets.Count == 1)
46         return GetConnection((string)_buckets[0]);
47
48     int tries = 0;
49
50     // generate hashcode
51     int hv;
52     if(hashCode != null)
53     {
54         hv = (int)hashCode;
55     }
56     else
57     {
58
59         // NATIVE_HASH = 0
60         // OLD_COMPAT_HASH = 1
61         // NEW_COMPAT_HASH = 2
62         switch(_hashingAlgorithm)
63         {
64             case HashingAlgorithm.Native:
65                 hv = key.GetHashCode();
66                 break;
67
68             case HashingAlgorithm.OldCompatibleHash:
69                 hv = OriginalHashingAlgorithm(key);
70                 break;
71
72             case HashingAlgorithm.NewCompatibleHash:
73                 hv = NewHashingAlgorithm(key);
74                 break;
75
76             default:
77                 // use the native hash as a default
78                 hv = key.GetHashCode();
79                 _hashingAlgorithm = HashingAlgorithm.Native;
80                 break;
81         }
82     }
83
84     // keep trying different servers until we find one
85     while(tries++ <= _buckets.Count)
86     {
87         // get bucket using hashcode
88         // get one from factory
89         int bucket = hv % _buckets.Count;
90         if(bucket < 0)
91             bucket += _buckets.Count;
92
93         SockIO sock = GetConnection((string)_buckets[bucket]);
94
95         if(Log.IsDebugEnabled)
96         {
97             Log.Debug(GetLocalizedString("cache choose").Replace("$$Bucket$$", _buckets[bucket].ToString()).Replace("$$Key$$", key));
98         }
99
100         if(sock != null)
101             return sock;
102
103         // if we do not want to failover, then bail here
104         if(!_failover)
105             return null;
106
107         // if we failed to get a socket from this server
108         // then we try again by adding an incrementer to the
109         // current key and then rehashing
110         switch(_hashingAlgorithm)
111         {
112             case HashingAlgorithm.Native:
113                 hv += ((string)("" + tries + key)).GetHashCode();
114                 break;
115
116             case HashingAlgorithm.OldCompatibleHash:
117                 hv += OriginalHashingAlgorithm("" + tries + key);
118                 break;
119
120             case HashingAlgorithm.NewCompatibleHash:
121                 hv += NewHashingAlgorithm("" + tries + key);
122                 break;
123
124             default:
125                 // use the native hash as a default
126                 hv += ((string)("" + tries + key)).GetHashCode();
127                 _hashingAlgorithm = HashingAlgorithm.Native;
128                 break;
129         }
130     }
131
132     return null;
133 }
根据缓存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 != 0)
{
arg_25_0 = (IntPtr)((int)expr_1C + RuntimeHelpers.OffsetToStringData);
}
char* ptr = arg_25_0;
int num = 5381;
int num2 = num;
char* ptr2 = ptr;
int num3;
while ((num3 = (int)(*(ushort*)ptr2)) != 0)
{
num = ((num << 5) + num ^ num3);
num3 = (int)(*(ushort*)(ptr2 + (IntPtr)2 / 2));
if (num3 == 0)
{
break;
}
num2 = ((num2 << 5) + num2 ^ num3);
ptr2 += (IntPtr)4 / 2;
}
return num + num2 * 1566083941;
}

// 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 != 0)
{
arg_0F_0 = (IntPtr)((int)expr_06 + RuntimeHelpers.OffsetToStringData);
}
char* ptr = arg_0F_0;
int num = 5381;
int num2 = num;
char* ptr2 = ptr;
int num3;
while ((num3 = (int)(*(ushort*)ptr2)) != 0)
{
num = ((num << 5) + num ^ num3);
num3 = (int)(*(ushort*)(ptr2 + (IntPtr)2 / 2));
if (num3 == 0)
{
break;
}
num2 = ((num2 << 5) + num2 ^ num3);
ptr2 += (IntPtr)4 / 2;
}
return num + num2 * 1566083941;
}
//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 != 0)
{
arg_25_0 = (IntPtr)((int)expr_1C + RuntimeHelpers.OffsetToStringData);
}
char* ptr = arg_25_0;
int num = 352654597;
int num2 = num;
int* ptr2 = (int*)ptr;
int i;
for (i = this.Length; i > 2; i -= 4)
{
num = ((num << 5) + num + (num >> 27) ^ *ptr2);
num2 = ((num2 << 5) + num2 + (num2 >> 27) ^ ptr2[(IntPtr)4 / 4]);
ptr2 += (IntPtr)8 / 4;
}
if (i > 0)
{
num = ((num << 5) + num + (num >> 27) ^ *ptr2);
}
return num + num2 * 1566083941;
}
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.5、memcache分布式实现、Object.GetHashCode 方法、关于 HashCode做key的可能性。
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-106507-1-1.html 上篇帖子: 分布式缓存MemCache 下篇帖子: Memcache的问题集
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表