经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C# » 查看文章
memcached缓存分布式部署方案
来源:cnblogs  作者:陈苏乾  时间:2018/10/12 9:49:42  对本文有异议

一、分布式方案介绍

比较流行的两种方案:

1.取余分布:

计算key的哈希值,与服务器数量取余,得到目标服务器。优点:实现简单,当某台服务器不可用时,故障转移方便;缺点:当增减服务器时, Key与服务器取余变动量较大,缓存重组代价极大。

代码实现可参考开源组件Memcached.ClientLibrary下的SockIOPool,源码地址:

https://sourceforge.net/p/memcacheddotnet/code/HEAD/tree/trunk/clientlib/src/clientlib/SockIOPool.cs

2.一致性哈希环分布:

其原理参考

https://www.cnblogs.com/lpfuture/p/5796398.html

http://www.zsythink.net/archives/1182

这两位老哥写的很清楚和直白,很容易理解。

一致性哈希环分布需要物理节点和虚拟节点,且虚拟节点对应到物理节点的服务器上。

二、代码实现

由于Memcached.ClientLibrary的作者已出取余分布的实现,这里不再叙述,以下代码和测试均是一致性哈希分布的。

1.数据结构:

服务器列表:List<string> servers;//IP:PORT

服务器虚拟节点数:List<int> weights;//与servers一一对应,灵活设置每server的不同虚拟节点数

节点存储结构:SortedDictionary<long, String> buckets;

Key:long类型,存储节点的hash%2^32;

Value:String类型,存储节点,即IP:PORT;

2.代码

计算哈希值算法,参考Memcached.ClientLibrary下的SockIOPool:

https://sourceforge.net/p/memcacheddotnet/code/HEAD/tree/trunk/clientlib/src/clientlib/SockIOPool.cs

        private int CalculateHashValue(String key)

        {

            int hv;

            switch (_hashingAlgorithm)

            {

                case EnumHashingAlgorithm.Native:

                    hv = key.GetHashCode();

                    break;

 

                case EnumHashingAlgorithm.OldCompatibleHash:

                    hv = HashingAlgorithmTool.OriginalHashingAlgorithm(key);

                    break;

 

                case EnumHashingAlgorithm.NewCompatibleHash:

                    hv = HashingAlgorithmTool.NewHashingAlgorithm(key);

                    break;

 

                default:

                    // use the native hash as a default

                    hv = key.GetHashCode();

                    _hashingAlgorithm = EnumHashingAlgorithm.Native;

                    break;

            }

            return hv;

        }

 

经过测试,OldCompatibleHash方式计算的哈希值比较散列。

  1. //哈希取余值,为什么是2的32次方:IPV4的总量是2的32次方个,可以保证环上的IP不重复
  2. long HashValue = (long)Math.Pow(2, 32);
  3. Key生成一致性哈希环中的哈希值
  4. private long GenerateConsistentHashValue(String key)
  5. {
  6. long serverHV = CalculateHashValue(key);
  7. long mod = serverHV % HashValue;
  8. if (mod < 0)
  9. {
  10. mod = mod + HashValue;
  11. }
  12. return mod;
  13. }

将Servers生成节点(物理+虚拟):

  1. private void GenerateServersToBuckets()
  2. {
  3. for (int i = 0; i < _servers.Count; i++)
  4. {
  5. // 创建物理节点
  6. String server = _servers[i];
  7. long mod = GenerateConsistentHashValue(server);
  8. buckets.Add(mod, server);
  9. //创建虚拟节点
  10. List<String> virtualHostServers = GenerateVirtualServer(server, this.Weights[i]);
  11. foreach (String v in virtualHostServers)
  12. {
  13. mod = GenerateConsistentHashValue(v);
  14. buckets.Add(mod, server);
  15. }
  16. }
  17. }

根据物理节点生成虚拟节点

  1. private static List<String> GenerateVirtualServer(String server, int count)
  2. {
  3. if (count > 255)
  4. {
  5. throw new ArgumentException("每服务器虚拟节点数不能超过254");
  6. }
  7. List<String> virtualServers = new List<string>();
  8. #region 1.按修改IP值+随机GUID生成虚拟节点
  9. String[] ipaddr = server.Split(':');
  10. String ip = ipaddr[0];
  11. string port = ipaddr[1];
  12. int header = Convert.ToInt32(ip.Split('.')[0]);
  13. String invariantIPPart = ip.Substring(ip.IndexOf('.'));
  14. int succ = 0;
  15. for (int i = 1; i <= 255; i++)
  16. {
  17. if (i != header)
  18. {
  19. String virtualServer = i.ToString() + invariantIPPart + ":" + port + i;// Guid.NewGuid().ToString("N").ToUpper();
  20. virtualServers.Add(virtualServer);
  21. succ++;
  22. }
  23. if (succ == count)
  24. {
  25. break;
  26. }
  27. }
  28. #endregion
  29.  
  30. #region 2.物理节点自增序号||随机GUID
  31. //for (int i = 0; i < count; i++)
  32. //{
  33. // //virtualServers.Add(server + i.ToString());
  34. // virtualServers.Add(server + i.ToString()+Guid.NewGuid().ToString());
  35. //}
  36. #endregion
  37.  
  38. #region 32.其它生成算法
  39. //TODO
  40. #endregion
  41.  
  42. return virtualServers;
  43. }

 

三、节点分布测试

四台服务器:{ "192.168.1.100:11211", "192.168.1.101:11211", "192.168.1.102:11211", "192.168.1.103:11211" }

哈希算法不同,则节点分布规则不同

1.物理节点分布

2.每物理节点10虚拟节点

节点分布测试结果:

节点数共有(物理4+虚拟4*10):44

在第一个节点和第二个节点间:

服务器A的虚拟节点数:1    占比:10%

服务器B的虚拟节点数:1    占比:10%

服务器C的虚拟节点数:0    占比:0%

服务器D的虚拟节点数:2    占比:20%

在第二个节点和第三个节点间:

服务器A的虚拟节点数:0    占比:0%

服务器B的虚拟节点数:0    占比:0%

服务器C的虚拟节点数:2    占比:20%

服务器D的虚拟节点数:2    占比:20%

在第三个节点和第四个节点间:

服务器A的虚拟节点数:4    占比:40%

服务器B的虚拟节点数:5    占比:50%

服务器C的虚拟节点数:4    占比:40%

服务器D的虚拟节点数:4    占比:40%

在第四个节点和第一个节点间:

服务器A的虚拟节点数:5    占比:50%

服务器B的虚拟节点数:4    占比:40%

服务器C的虚拟节点数:4    占比:40%

服务器D的虚拟节点数:2    占比:20%

 3.每物理节点30虚拟节点

节点分布测试结果:

节点数共有(物理4+虚拟4*30):124

在第一个节点和第二个节点间:

服务器A的虚拟节点数:7    占比:23%

服务器B的虚拟节点数:7    占比:23%

服务器C的虚拟节点数:6    占比:20%

服务器D的虚拟节点数:7    占比:23%

在第二个节点和第三个节点间:

服务器A的虚拟节点数:3    占比:10%

服务器B的虚拟节点数:1    占比:3%

服务器C的虚拟节点数:4    占比:13%

服务器D的虚拟节点数:4    占比:13%

在第三个节点和第四个节点间:

服务器A的虚拟节点数:11    占比:36%

服务器B的虚拟节点数:11    占比:36%

服务器C的虚拟节点数:10    占比:33%

服务器D的虚拟节点数:10    占比:33%

在第四个节点和第一个节点间:

服务器A的虚拟节点数:9    占比:30%

服务器B的虚拟节点数:11    占比:36%

服务器C的虚拟节点数:10    占比:33%

服务器D的虚拟节点数:9    占比:30%

4. 每物理节点50虚拟节点:.

 

 

节点分布测试结果:

节点数共有(物理4+虚拟4*50):204

在第一个节点和第二个节点间:

服务器A的虚拟节点数:14    占比:28%

服务器B的虚拟节点数:13    占比:26%

服务器C的虚拟节点数:12    占比:24%

服务器D的虚拟节点数:13    占比:26%

在第二个节点和第三个节点间:

服务器A的虚拟节点数:4    占比:8%

服务器B的虚拟节点数:3    占比:6%

服务器C的虚拟节点数:5    占比:10%

服务器D的虚拟节点数:7    占比:14%

在第三个节点和第四个节点间:

服务器A的虚拟节点数:17    占比:34%

服务器B的虚拟节点数:18    占比:36%

服务器C的虚拟节点数:16    占比:32%

服务器D的虚拟节点数:16    占比:32%

在第四个节点和第一个节点间:

服务器A的虚拟节点数:15    占比:30%

服务器B的虚拟节点数:16    占比:32%

服务器C的虚拟节点数:17    占比:34%

服务器D的虚拟节点数:14    占比:28%

5. 每物理节点80虚拟节点

 

 

节点分布测试结果:

节点数共有(物理4+虚拟4*80):324

在第一个节点和第二个节点间:

服务器A的虚拟节点数:22    占比:27%

服务器B的虚拟节点数:23    占比:28%

服务器C的虚拟节点数:21    占比:26%

服务器D的虚拟节点数:22    占比:27%

在第二个节点和第三个节点间:

服务器A的虚拟节点数:7    占比:8%

服务器B的虚拟节点数:5    占比:6%

服务器C的虚拟节点数:9    占比:11%

服务器D的虚拟节点数:10    占比:12%

在第三个节点和第四个节点间:

服务器A的虚拟节点数:27    占比:33%

服务器B的虚拟节点数:27    占比:33%

服务器C的虚拟节点数:25    占比:31%

服务器D的虚拟节点数:24    占比:30%

在第四个节点和第一个节点间:

服务器A的虚拟节点数:24    占比:30%

服务器B的虚拟节点数:25    占比:31%

服务器C的虚拟节点数:25    占比:31%

服务器D的虚拟节点数:24    占比:30%

6. 每物理节点100虚拟节点

 

 

节点分布测试结果:

节点数共有(物理4+虚拟4*100):404

在第一个节点和第二个节点间:

服务器A的虚拟节点数:29    占比:29%

服务器B的虚拟节点数:30    占比:30%

服务器C的虚拟节点数:28    占比:28%

服务器D的虚拟节点数:28    占比:28%

在第二个节点和第三个节点间:

服务器A的虚拟节点数:8    占比:8%

服务器B的虚拟节点数:8    占比:8%

服务器C的虚拟节点数:11    占比:11%

服务器D的虚拟节点数:12    占比:12%

在第三个节点和第四个节点间:

服务器A的虚拟节点数:33    占比:33%

服务器B的虚拟节点数:32    占比:32%

服务器C的虚拟节点数:30    占比:30%

服务器D的虚拟节点数:30    占比:30%

在第四个节点和第一个节点间:

服务器A的虚拟节点数:30    占比:30%

服务器B的虚拟节点数:30    占比:30%

服务器C的虚拟节点数:31    占比:31%

服务器D的虚拟节点数:30    占比:30%

说明:由于统计计算时按int取值,服务器虚拟节点比率总和可能有1的误差。

总结:以上可以看出当总节点在300以上时,各物理节点之间的虚拟节点所占比率变化较小,说明分布比较均匀。

四、存取数据查找服务器

原理:根据数据的Key与HashValue取余值hv,查找buckets中Key>=hv的第一个服务器,即是Key的目标服务器,当返回的服务器不可用时,还可以进行故障转移

1.从节点环中查找服务器

  1. private String FindServer(String key, ref long startIndex)
  2. {
  3. long mod = startIndex;
  4. if (mod < 0)
  5. {
  6. mod = GenerateConsistentHashValue(key);
  7. }
  8. foreach (KeyValuePair<long, String> kvp in buckets)
  9. {
  10. startIndex = kvp.Key;
  11. //找到第一个大于或等于key的服务器
  12. if (kvp.Key >= mod)
  13. {
  14. //若找到的服务器不可用,则继续查找下一服务器
  15. if (_hostDead.ContainsKey(kvp.Value))
  16. {
  17. continue;
  18. }
  19. return kvp.Value;
  20. }
  21. }
  22. //如果大于mod的服务器均不可用或没有找到,则从头开始找可用服务器
  23. foreach (KeyValuePair<long, String> kvp in buckets)
  24. {
  25. startIndex = kvp.Key;
  26. if (kvp.Key >= mod)
  27. {
  28. break;
  29. }
  30. if (_hostDead.ContainsKey(kvp.Value))
  31. {
  32. continue;
  33. }
  34. return kvp.Value;
  35. }
  36. //不存在可用服务器
  37. return string.Empty;
  38. }

2.获取服务器及连接

  1. public ISockIO GetSock(string key)
  2. {
  3. if (buckets.Count == 0)
  4. {
  5. return null;
  6. }
  7. if (buckets.Count == 1)
  8. {
  9. return GetConnection(buckets[0]);
  10. }
  11. long startIndex = -1;//开始查找位置,-1表示从hash(key)% HashValue位置开始查找
  12. int tries = 0;//重试次数,不超过总服务器数
  13. while (tries++ <= this.servers.Count)
  14. {
  15. String server = FindServer(key, ref startIndex);
  16. //找不到可用的服务器
  17. if (String.IsNullOrEmpty(server))
  18. {
  19. return null;
  20. }
  21. ISockIO sock = GetConnection(server);
  22. if (sock != null)
  23. {
  24. WriteLog.Write("key:" + key + ",server:" + server);
  25. return sock;
  26. }
  27. //是否需要故障转移,若需要,则会继续查找可用的服务器
  28. if (!_failover)
  29. {
  30. return null;
  31. }
  32. }
  33. return null;
  34. }

 

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号