阅读目录:

  1. 使用场景
  2. 算法原理
  3. 虚拟节点
  4. 代码示例

使用场景

以Redis为例,当系统需要缓存的内容超过单机内存大小时,例如要缓存100G数据,单机内存仅有16G时。这时候就需要考虑进行缓存数据分片,也即是把100G的数据拆分成多块小于单机内存的数据。例如以10G为单位,拆分10份,存储到多台机器节点上。 但是数据怎么个分法更合理呢? 、

f(key)%n

这里配置n=10,不同的key根据数值余数映射到对应的机器。 很简单的办法就解决了多台节点key分法的问题。然而数据大小的增长和缩减是很难预知的, 如果需要增加一台缓存服务器。 配置n=11,会发现之前根据余数建立的映射关系发生混乱。映射错乱后,就会发生大量key无法命中正确的节点,需要全部重新进行映射。  如果以后再添加节点,同样会遇到这样问题。

    servers = ['redis:6379', 'redis:6380', 'redis:6381']
server = servers[f(key) % servers.length]

一致性hash(consistent hashing)

为了降低添加或删除服务器节点,导致大量key无法命中的影响。就提出了一种更为合理的分法,也即是一致性hash算法。 下面看下为什么更合理些?

算法原理

空间归属

我们在脑中假想下:每台节点以CHash(ip)形式计算出一个数值,n台机器有n个数值。 把数值首尾相连,形成一个虚拟圆环的数值空间。
例如有3台机器:

servers =['redis:6379', 'redis:6380', 'redis:6381']。

CHash(server[])==
Chash(server[])==
CHash(server[])==

把机器计算得到的数值,在虚拟圆环中按照顺时针方向来确定空间归属,得到:

100~200空间属于6379管。
200~300空间属于6380管。
300+,100-空间属于6381管。

key坐标

这时有3个key要存储到redis,分别是key1—key3。 通过CHash函数计算出3个key的数值坐标:

CHash(key1)=
CHash(key2)=
CHash(key3)=

空间映射

求出key的数值坐标后,就知道key与机器节点的映射关系。 即key1应存储在6379,key3存储到6381。

添加节点

由于缓存数据的增加,需要添加一台新节点6382。计算出空间数值:

CHash()==  

那么他在虚拟圆环中的位置如下:

从图中得知,6379、6381的数值空间区域没任何变化,它们存储的key依旧可以正常命中的。
优点之一:对现有缓存的命中影响较小。
但原本6380的区域200~300被6382侵入了。 6382的空间数值250正好划分一半,即200~250的区域还归6380管,但250~300的区域却归新来的管了。 (为示例而使用简单数字区分,实际上没这么精准)
优点之二:实现对数据的分片
同时也带来了缺点就是: 原本存储在6380(250~300这部分)的旧缓存数据就无法命中了,要去新的6382拿。 所以说一致性hash并不能完全解决这种影响,只能尽量降低。

移除节点

与添加节点同理。比如拿掉新加的6382,250~300区域还管原来的6380管,当然6382这部分缓存也就丢了。

虚拟节点

一致性Hash虽然实现了数据分片,但由于节点较少,key有可能会大量集中到某一台上面,导致缓存分布不均匀。 特别是在只有几台或十几台机器节点时。
为了降低这种影响,一致性hash算法提出虚拟节点的解决方案。 即一个物理机器节点对应着多个虚拟节点。 这里配置一个物理节点对应2个虚拟节点,此时应为:

={6379A,6379B}
={6380A,6380B}
={6381A,6381B}

这样成了6个节点了(可以配置更多),它们同样在虚拟圆环上按数值顺时针排列。由于节点变多,对应的数值区域也变大。使key进行数值空间映射时变的更加离散性,从概率上来提高key的均匀分布。

原本需要计算真实节点数值,也变成计算虚拟节点数值, 然后由虚拟节点的数值构成虚拟圆环数值空间。其中每一组虚拟节点数值,对应单个物理节点。

servers= ['redis:6379', 'redis:6380', 'redis:6381'];

//下面f函数中先将servers与虚拟节点映射成 6379={6379A,6379B}, 6380={6380A,6380B},6381={6381A,6381B}
// 在对虚拟节点求各自的数值,而数值对应的还是物理节点。即:
vservers = f(servers) ={['redis:6379','100'],['redis:6379','300'] ....,['redis:6381',150]};

CHash(key1)==102 ∈ vservers[0] 
......
CHash(key3)==350 ∈ vservers[1]

虚拟节点使key分布的更加均衡,但不能解决添加机、删除节点带来的影响。

代码示例

1:使用字典模拟虚拟圆环,并添加节点。

2:计算key数值,应该归属到哪个节点数值空间区域。

3:计算分布频率。复制的虚拟节点越多,分布越平均。

        private static readonly SortedDictionary<ulong, string> _circle = new SortedDictionary<ulong, string>();
static void Main(string[] args)
{
int Replicas = ;
AddNode("127.0.0.1:6379", Replicas);
AddNode("127.0.0.1:6380", Replicas);
AddNode("127.0.0.1:6381", Replicas);
List<string> nodes = new List<string>();
for (int i = ; i < ; i++)
{
nodes.Add(GetTargetNode(i + "test" + (char)i));
}
var counts = nodes.GroupBy(n => n, n => n.Count()).ToList();
counts.ForEach(index => Console.WriteLine(index.Key+"-"+index.Count()));
Console.ReadLine();
}

输出:

127.0.0.1:6380-39
127.0.0.1:6381-29
127.0.0.1:6379-32

虚拟圆环的值:

其余代码:

 public static void AddNode(string node, int repeat)
{
for (int i = ; i < repeat; i++)
{
string identifier = node.GetHashCode().ToString() + "-" + i;
ulong hashCode = Md5Hash(identifier);
_circle.Add(hashCode, node);
}
} public static ulong Md5Hash(string key)
{
using (var hash = System.Security.Cryptography.MD5.Create())
{
byte[] data = hash.ComputeHash(Encoding.UTF8.GetBytes(key));
var a = BitConverter.ToUInt64(data, );
var b = BitConverter.ToUInt64(data, );
ulong hashCode = a ^ b;
return hashCode;
}
}
public static string GetTargetNode(string key)
{
ulong hash = Md5Hash(key);
ulong firstNode = ModifiedBinarySearch(_circle.Keys.ToArray(), hash);
return _circle[firstNode];
} /// <summary>
/// 计算key的数值,得出空间归属。
/// </summary>
/// <param name="sortedArray"></param>
/// <param name="val"></param>
/// <returns></returns>
public static ulong ModifiedBinarySearch(ulong[] sortedArray, ulong val)
{
int min = ;
int max = sortedArray.Length - ; if (val < sortedArray[min] || val > sortedArray[max])
return sortedArray[]; while (max - min > )
{
int mid = (max + min) / ;
if (sortedArray[mid] >= val)
{
max = mid;
}
else
{
min = mid;
}
} return sortedArray[max];
}

探索c#之一致性Hash详解的更多相关文章

  1. location.hash详解

    一.#的涵义 #代表网页中的一个位置.其右面的字符,就是该位置的标识符.比如, http://www.example.com/index.html#print 就代表网页index.html的prin ...

  2. location.hash 详解

    前年9月twitter改版. 一个显著变化,就是URL加入了"#!"符号.比如,改版前的用户主页网址为 http://twitter.com/username 改版后,就变成了 h ...

  3. hash详解

    首先介绍一下hash? 事实上是一种叫做蛤丝的病毒 hash的做法: 首先设一个进制数base,并设一个模数mod 而哈希其实就是把一个数转化为一个值,这个值是base进制的,储存在哈希表中,注意一下 ...

  4. asp.net core microservices 架构之Task 事务一致性 事件源 详解

    一 aspnetcore之task的任务状态-CancellationToken 我有一篇文章讲解了asp.net的线程方面的知识.我们知道.net的针对于多线程的一个亮点就是Task,net clr ...

  5. 探索C++的秘密之详解extern "C",这就是为什么很多.lib被我们正确调用确总是无法解析的。

    (转载,绝对的有用) lib被我们正确调用确总是无法解析.这是C++编译和C编译的区别 时常在cpp的代码之中看到这样的代码: #ifdef __cplusplus extern "C&qu ...

  6. 【转】探索C++的秘密之详解extern

    本文转自:http://i.cnblogs.com/EditPosts.aspx?opt=1 时常在cpp的代码之中看到这样的代码: #ifdef __cplusplus extern "C ...

  7. (转)探索C++的秘密之详解extern "C",这就是为什么很多.lib被我们正确调用确总是无法解析的。

    (转载,绝对的有用) lib被我们正确调用确总是无法解析.这是C++编译和C编译的区别 时常在cpp的代码之中看到这样的代码: #ifdef __cplusplus extern "C&qu ...

  8. Webpack探索【5】--- plugins详解

    本文主要讲plugins相关内容. https://gitbook.cn/gitchat/column/59e065f64f7fbe555e479204/topic/59e96d87a35cf44e1 ...

  9. Webpack探索【3】--- loader详解

    本文主要说明Webpack的loader相关内容.

随机推荐

  1. Beginning Scala study note(1) Geting Started with Scala

    1. Scala is a contraction of "scalable" and "language". It's a fusion of objecte ...

  2. 【java.lang.UnsupportedClassVersionError】版本不一致出错

    这种错误的全部报错信息: java.lang.UnsupportedClassVersionError: org/apache/lucene/store/Directory : Unsupported ...

  3. LeetCode 1. Two Sum

    Problem: Given an array of integers, return indices of the two numbers such that they add up to a sp ...

  4. 苹果手机微信上form表单提交的问题

    场景:前端页面请求后端php,返回带form表单dom元素,然后将其追在页面上,返回的html字段中包含表单自动提交的代码,想法是将带有表单自动提交的dom元素追加到页面上,然后表单自动提交到另外一个 ...

  5. 第三章 一个简单的机器学习例子让你了解DeepLab的语言风格

    MINST是由Yann LeCun等人建立并维护的手写数字识别数据库.该数据库总共包含60000个训练样本和10000个测试样本.其中每个样本的大小是一张28*28的手写数字图片.数字包括从0~9总共 ...

  6. nodejs redis 发布订阅机制封装

    最近项目使用redis,对publish 和 subscribe的使用进行了了解,并进行了封装. var config = require('../config/config'); var log = ...

  7. A library of generic data structures

    A library of generic data structures including a list, array, hashtable, deque etc.. https://github. ...

  8. iOS tableview自定义cell上添加按钮实现删除功能

    在删除的时候,先删除数据源,再删除cell 但是,会发现一直崩: numberOfRowsInSection 解决方案:

  9. IE6中内容高度比高级浏览器高的解决办法

    1.div高度小于12px时,加over-flow:hidden; 2.多用padding,少用margin: 3.img vertical-align:top;

  10. POJ 2519

    又是一个水题?? 不过还是弄是很久... 在蒟蒻的路上越走越远 , 好了讲题 新生晚会 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: ...