参考:
https://zh.wikipedia.org/wiki/%E4%B8%80%E8%87%B4%E5%93%88%E5%B8%8C
1、概念
一致哈希 是一种特殊的哈希算法。在使用一致哈希算法后,哈希表槽位数(大小)的改变平均只需要对 个关键字重新映射,其中 是关键字的数量,是槽位数量。然而在传统的哈希表中,添加或删除一个槽位的几乎需要对所有关键字进行重新映射。
2、问题
在使用n台缓存服务器来缓存m个资源时,一种常用的负载均衡方式是,对资源o的请求使用hash(o) = o mode n 来映射到某一台缓存服务器,这种方法可以有效的将m资源请求均衡的分散在n个节点。
代码示例:
void hash_test(){ int items = 10000; int nodes = 10; int node_stat[nodes]; long hash_key = 0; for(int i = 0;i < items;i++){ char buf[128]; snprintf(buf,127,"%s-00%d","object",i); hash_key = conhash_hash_fun_md5(buf); node_stat[hash_key%nodes]++; } int avg = items/10; int max = get_max(node_stat,nodes); int min = get_min(node_stat,nodes); printf("avg valus is %d\n",items/nodes); printf("max valus is %d (%2.2f %%) \n",max,max*100.0/avg); printf("min valus is %d (%2.2f %%) \n",min,min*100.0/avg); }
输出结果:
avg valus is 1000
max valus is 1068 (106.80 %)
min valus is 959 (95.6 %)
从上面的输出结果我们可以看出10000个资源几乎是均匀散落在10个节点上面,每个节点散落的资源和平均值的差值都在6%之内。
但是该方法会带来比较严重的后果,就是当增加或减少一台缓存服务器时这种方式可能会改变所有资源对应的hash值,也就是所有的缓存都失效了,这会使得缓存服务器大量集中地向原始内容服务器更新缓存。
代码示例:
int items = 10000; int nodes = 10; int nodes2 = 11; long hash_key = 0; int change = 0; for(int i = 0;i < items;i++){ char buf[128]; snprintf(buf,127,"%s-00%d","object",i); hash_key = conhash_hash_fun_md5(buf); if((hash_key % nodes) != (hash_key % nodes2)) change++; } printf("%d (%2.3f%%)\n",change,change*100.0/items);
输出结果:
change is 9078 (90.780%)
从上面的结果我们可以看到一当增加(或减少)了一个节点,超过90%的缓存改变了其散落的节点,也就是说超过90%的缓存数据需要做迁移。
需要一种新的算法来避免这样的问题,该算法尽可能使得同一个资源映射到同一个节点上。这种方式要求增加一个节点时候,新的节点尽量分担存储其他节点缓存的资源。减少一台服务器时候,其他节点也可以尽量分担存储资源。
3、一致性哈希
一致哈希将每个对象映射到圆环边上的一个点,系统再将可用的节点机器映射到圆环的不同位置。查找某个对象对应的机器时,需要用一致哈希算法计算得到对象对应圆环边上位置,沿着圆环边上查找直到遇到某个节点机器,这台机器即为对象应该保存的位置。 当删除一台节点机器时,这台机器上保存的所有对象都要移动到下一台机器。添加一台机器到圆环边上某个点时,这个点的下一台机器需要将这个节点前对应的对象移动到新机器上。 更改对象在节点机器上的分布可以通过调整节点机器的位置来实现。
增加一个节点:
减少一个节点: