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

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

  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 == )
  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 == )
  42. return null;
  43.  
  44. // if only one server, return it
  45. if(_buckets.Count == )
  46. return GetConnection((string)_buckets[]);
  47.  
  48. int tries = ;
  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 < )
  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服务器上,数据的一致性无法保证,清除缓存的代码也可能失效。

  1. // 64位 4.0
  2. [__DynamicallyInvokable, ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), SecuritySafeCritical]
  3. public unsafe override int GetHashCode()
  4. {
  5. if (HashHelpers.s_UseRandomizedStringHashing)
  6. {
  7. return string.InternalMarvin32HashString(this, this.Length, 0L);
  8. }
  9. IntPtr arg_25_0;
  10. IntPtr expr_1C = arg_25_0 = this;
  11. if (expr_1C != )
  12. {
  13. arg_25_0 = (IntPtr)((int)expr_1C + RuntimeHelpers.OffsetToStringData);
  14. }
  15. char* ptr = arg_25_0;
  16. int num = ;
  17. int num2 = num;
  18. char* ptr2 = ptr;
  19. int num3;
  20. while ((num3 = (int)(*(ushort*)ptr2)) != )
  21. {
  22. num = ((num << ) + num ^ num3);
  23. num3 = (int)(*(ushort*)(ptr2 + (IntPtr) / ));
  24. if (num3 == )
  25. {
  26. break;
  27. }
  28. num2 = ((num2 << ) + num2 ^ num3);
  29. ptr2 += (IntPtr) / ;
  30. }
  31. return num + num2 * ;
  32. }
  33.  
  34. // 64位 2.0
  35. // string
  36. [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
  37. public unsafe override int GetHashCode()
  38. {
  39. IntPtr arg_0F_0;
  40. IntPtr expr_06 = arg_0F_0 = this;
  41. if (expr_06 != )
  42. {
  43. arg_0F_0 = (IntPtr)((int)expr_06 + RuntimeHelpers.OffsetToStringData);
  44. }
  45. char* ptr = arg_0F_0;
  46. int num = ;
  47. int num2 = num;
  48. char* ptr2 = ptr;
  49. int num3;
  50. while ((num3 = (int)(*(ushort*)ptr2)) != )
  51. {
  52. num = ((num << ) + num ^ num3);
  53. num3 = (int)(*(ushort*)(ptr2 + (IntPtr) / ));
  54. if (num3 == )
  55. {
  56. break;
  57. }
  58. num2 = ((num2 << ) + num2 ^ num3);
  59. ptr2 += (IntPtr) / ;
  60. }
  61. return num + num2 * ;
  62. }
  63.  
  64. //32位 4.0
  65. [__DynamicallyInvokable, ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), SecuritySafeCritical]
  66. public unsafe override int GetHashCode()
  67. {
  68. if (HashHelpers.s_UseRandomizedStringHashing)
  69. {
  70. return string.InternalMarvin32HashString(this, this.Length, 0L);
  71. }
  72. IntPtr arg_25_0;
  73. IntPtr expr_1C = arg_25_0 = this;
  74. if (expr_1C != )
  75. {
  76. arg_25_0 = (IntPtr)((int)expr_1C + RuntimeHelpers.OffsetToStringData);
  77. }
  78. char* ptr = arg_25_0;
  79. int num = ;
  80. int num2 = num;
  81. int* ptr2 = (int*)ptr;
  82. int i;
  83. for (i = this.Length; i > ; i -= )
  84. {
  85. num = ((num << ) + num + (num >> ) ^ *ptr2);
  86. num2 = ((num2 << ) + num2 + (num2 >> ) ^ ptr2[(IntPtr) / ]);
  87. ptr2 += (IntPtr) / ;
  88. }
  89. if (i > )
  90. {
  91. num = ((num << ) + num + (num >> ) ^ *ptr2);
  92. }
  93. return num + num2 * ;
  94. }

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. WTFPL 开源协议

    中文翻译: 你他妈的随便公共许可 版本2, 2004年12月 版权所有(C) 2004 Sam Hocevar <sam@hocevar.net> 每个人都允许复制和散布或修改本授权文件的 ...

  2. TclError: no display name and no $DISPLAY environment variable

    %matplotlib inline 或 %matplotlib notebook

  3. .NET Framework3.0/3.5/4.0/4.5新增功能摘要

    Microsoft .NET Framework 3.0 .NET Framework 3.0 中增加了不少新功能,例如: Windows Workflow Foundation (WF) Windo ...

  4. 【开源】开发者新闻聚合APP 1.0.3发布(第一个稳定版本,短期内不再发布新版本)

    聚合了博客园新闻.infoq新闻.36kr新闻.oschina新闻.51cto新闻.csdn新闻: 争取做到随时刷随时有开发者的新闻! 目前还只支持安卓APP 最新版本的下载地址:https://gi ...

  5. java提高篇(十六)-----异常(一)

         Java的基本理念是“结构不佳的代码不能运行”!!!!! 大成若缺,其用不弊. 大盈若冲,其用不穷. 在这个世界不可能存在完美的东西,不管完美的思维有多么缜密,细心,我们都不可能考虑所有的因 ...

  6. MySQL模糊查询(like)时区分大小写

    问题说明:通过上面的语句,你会发现MySQL的like查询是不区分大小写的,因为我的失误,把Joe写成了joe才发现了这个东东吧.但是,有时候,我们需要区分大小写的是,该怎么办呢?解决方法如下: 方法 ...

  7. 记录自己在使用Bootstrap中的心得

    一.网格系统 在做CRM OP后台时,直接在前人的的一些页面上进行了修改和增加,发现一些东西增加字段后有问题,比如网格系统,怎么改样式都不对,最后自己没法发,做成了半响应式的了.今天重新看Bootst ...

  8. Andrew Ng机器学习公开课笔记 -- Regularization and Model Selection

    网易公开课,第10,11课 notes,http://cs229.stanford.edu/notes/cs229-notes5.pdf   Model Selection 首先需要解决的问题是,模型 ...

  9. Java-继承,多态-0922-04

    定义类Human,具有若干属性和功能:定义其子类Man.Woman: 在主类Test中分别创建子类.父类和上转型对象,并测试其特性. 父类: package com.lianxi3; public c ...

  10. [数据库连接池] Java数据库连接池--DBCP浅析.

    前言对于数据库连接池, 想必大家都已经不再陌生, 这里仅仅设计Java中的两个常用数据库连接池: DBCP和C3P0(后续会更新). 一. 为何要使用数据库连接池假设网站一天有很大的访问量,数据库服务 ...