下面是程老师博客地址及原文:http://flychao88.iteye.com/blog/1540246
一致性哈希算法是分布式系统中常用的算法。比如,一个分布式的存储系统,要将数据存储到具体的节点上,如果采用普通的hash方法,将数据映射到具体的节点上,如key%N,key是数据的key,N是机器节点数,如果有一个机器加入或退出这个集群,则所有的数据映射都无效了,如果是持久化存储则要做数据迁移,如果是分布式缓存,则其他缓存就失效了。
一致性Hash算法将 value 映射到一个 32 为的 key 值,也即是 0~2^32-1 次方的数值空间;我们可以将这个空间想象成一个首( 0 )尾( 2^32-1 )相接的圆环。
如下图所示:
一致性Hash算法将 value 映射到一个 32 为的 key 值,也即是 0~2^32-1 次方的数值空间;我们可以将这个空间想象成一个首( 0 )尾( 2^32-1 )相接的圆环。
如下图所示:
*************************************原文结束,学习笔记如下**********************************
https://www.codeproject.com/Articles/56138/Consistent-hashing
这篇讲的比较详细。
直接看概念是比较空洞的,不容易理解。而且程老师的下面的代码经过测试有问题。
part1,学习问题背景,及改进思路。
part2.修改demo,验证算法结果。
*******************************************part1**************************************************
问题背景看文章介绍大概了解,那么解决的核心思想就是:“虚拟节点”
[引文]使用虚拟节点的思想,为每个物理节点(服务器)在圆上分配100~200个点。这样就能抑制分布不均匀,最大限度地减小服务器增减时的缓存重新分布。用户数据映射在虚拟节点上,就表示用户数据真正存储位置是在该虚拟节点代表的实际物理服务器上。
图文并茂的需要看原文:
http://blog.csdn.net/cywosp/article/details/23397179/
作者解释的很清晰。
****************************************************part2*********************************************************
未修改之前,结果
node5 count:10000, Normal percent : 20.0%
**********detail*************
name:node1-times:2210-percent:22%
name:node0-times:669-percent:6%
name:node4-times:3062-percent:30%
name:node2-times:1804-percent:18%
name:node3-times:2255-percent:22%
5个节点,分部不均匀,达不到要求。
看了网上分析之后,主要是插入节点过少且没有均匀放到环上导致。
修改后运行结果:
node5 count:10000, Normal percent : 20.0%
**********detail*************
name:node1-times:2188-percent:21%
name:node0-times:2013-percent:20%
name:node4-times:1997-percent:19%
name:node2-times:1865-percent:18%
name:node3-times:1937-percent:19%
代码如下:
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
/**
*
* 一致性hash测试
*
* 2016年3月2日 下午3:02:59
*/
public class ConsistencyHash {
private TreeMap<Long,Object> ketamaNodes = null;
//真实服务器节点信息
private List<Object> shards = new ArrayList();
//设置虚拟节点数目
private int VIRTUAL_NUM = 160;
//记录node对应的times,统计用
private static Map<Object,Integer> resmap = new HashMap<Object,Integer>();
/**
* 初始化
*/
public List<Object> init(int n) {
//
for (int i=0;i<n;i++)
{
shards.add("node"+i);
resmap.put("node"+i, 0);
}
return shards;
}
public void KetamaNodeLocator(List<Object> nodes, int nodeCopies) {
ketamaNodes = new TreeMap<Long,Object>();
//对所有节点,生成nCopies个虚拟结点
for (Object node : nodes) {
//每四个虚拟结点为一组
for (int i = 0; i < nodeCopies / 4; i++)
{
//Md5是一个16字节长度的数组,将16字节的数组每四个字节一组, 分别对应一个虚拟结点
byte[] digest = computeMd5(node.toString() + i);
//对于每四个字节,组成一个long值数值,做为这个虚拟节点的在环中的惟一key
for(int j=0; j<4; j++) {
long m = hash(digest,j);
ketamaNodes.put(m, node);
}
}
}
}
/**
* 根据key的hash值取得服务器节点信息
* @param hash
* @return
*/
public Object getShardInfo(long hash) {
Long key = hash;
//如果找到这个节点,直接取节点,返回
if(!ketamaNodes.containsKey(key))
{
//得到大于当前key的那个子Map,然后从中取出第一个key,就是大于且离它最近的那个key
SortedMap<Long, Object> tailMap=ketamaNodes.tailMap(key);
if(tailMap.isEmpty()) {
key = ketamaNodes.firstKey();
} else {
key = tailMap.firstKey();
}
}
return ketamaNodes.get(key);
}
/**
* 打印圆环节点数据
*/
public void printMap() {
System.out.println(ketamaNodes.size()+":" );
}
/**
* 根据2^32把节点分布到圆环上面。
* @param digest
* @param nTime
* @return
*/
public long hash(byte[] digest, int nTime) {
long rv = ((long) (digest[3+nTime*4] & 0xFF) << 24)
| ((long) (digest[2+nTime*4] & 0xFF) << 16)
| ((long) (digest[1+nTime*4] & 0xFF) << 8)
| (digest[0+nTime*4] & 0xFF);
return rv & 0xffffffffL; /* Truncate to 32-bits */
}
/**
* Get the md5 of the given key.
* 计算MD5值
*/
public byte[] computeMd5(String k) {
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 not supported", e);
}
md5.reset();
byte[] keyBytes = null;
try {
keyBytes = k.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unknown string :" + k, e);
}
md5.update(keyBytes);
return md5.digest();
}
public static void main(String[] args) {
Random ran = new Random();
ConsistencyHash hash = new ConsistencyHash();
int node =5;
int count =10000;
List<Object> nodes= hash.init(node);
hash.KetamaNodeLocator(nodes,hash.VIRTUAL_NUM);
System.out.println("node"+node+" count:"+count+", Normal percent : " + (float) 100 /node + "%");
System.out.println("**********detail*************");
for(int i=0; i<count; i++) {
long tmp = hash.hash(hash.computeMd5(String.valueOf(i)),ran.nextInt(4));
Object o =hash.getShardInfo(tmp);
int sum =resmap.get(o);
sum++;
resmap.put(o, sum);
}
for(Object entry:resmap.keySet())
{
System.out.println("name:"+entry+"-times:"+resmap.get(entry)+"-percent:"+((resmap.get(entry)*100/count))+"%");
}
}
}