一致性哈希算法 - Consistent Hashing
- - CSDN博客云计算推荐文章一、简单介绍一致性哈希算法. 分布式存储中,常常涉及到负载均衡问题,由于有多个数据存储服务器. 因此当一个对象被保存时候,它究竟应该存放到哪个数据存储服务器上面呢. 又例如:现在假设有一个网站,最近发现随着流量增加,服务器压力越来越大,之前直接读写数据库的方式已经不能满足用户的访问,于是想引入 Memcached 作为缓存机制.

package webspider.consistenthash;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* hash函数,根据key生成hash
*
* @author typ
*
*/
public class HashFunction {
/**
* 用MD5压缩算法,生成hashmap的key值
*
* @param source
* @return
* @throws NoSuchAlgorithmException
*/
public String hash(String key) {
String s = null;
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
md.update(key.getBytes());
// MD5的结果:128位的长整数,存放到tmp中
byte tmp[] = md.digest();
s = toHex(tmp);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return s;
}
/**
* 将二进制的长整数转换为16进制的数字,以字符串表示
*
* @param bytes
* @return
*/
public String toHex(byte[] bytes) {
// hexDigits的用处是:将任意字节转换为十六进制的数字
char hexDigits[] = "0123456789abcdef".toCharArray();
// MD5的结果:128位的长整数,用字节表示就是16个字节,用十六进制表示的话,使用两个字符,所以表示成十六进制需要32个字符
char str[] = new char[16 * 2];
int k = 0;
for (int i = 0; i < 16; i++) {
byte b = bytes[i];
// 逻辑右移4位,与0xf(00001111)相与,为高四位的值,然后再hexDigits数组中找到对应的16进制值
str[k++] = hexDigits[b >>> 4 & 0xf];
// 与0xf(00001111)相与,为低四位的值,然后再hexDigits数组中找到对应的16进制值
str[k++] = hexDigits[b & 0xf];
}
String s = new String(str);
return s;
}
}
2、有了hash算法,我们来写实现环形hash空间的Consistent
Hashing算法,如下所示,关键地方参考注释:(核心地方就是get方法,看是如何实现一个逻辑的环形hash空间的) package webspider.consistenthash;
import java.util.Collection;
import java.util.HashSet;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* 利用一致性hash,计算得到对象要存放的服务器
*
* @author typ
*
* @param <T>
*/
public class ConsistentHash<T> {
// 哈希函数类,这个类由自己定义,可以用MD5压缩法
private final HashFunction hashFunction;
// 虚拟节点个数
private final int numberOfReplicas;
// 建立有序的map
private final SortedMap<String, T> circle = new TreeMap<String, T>();
public ConsistentHash(HashFunction hashFunction, int numberOfReplicas,
Collection<T> nodes) {
this.hashFunction = hashFunction;
this.numberOfReplicas = numberOfReplicas;
for (T node : nodes) {
add(node);
}
}
/**
* map中添加服务器节点
*
* @param node
*/
public void add(T node) {
String key;
// 虚拟节点所在的hash处,存放对应的实际的节点服务器
for (int i = 0; i < numberOfReplicas; i++) {
key = node.toString() + i;
circle.put(hashFunction.hash(key), node);
}
}
/**
* map中移除服务器节点
*
* @param node
*/
public void remove(T node) {
String key;
for (int i = 0; i < numberOfReplicas; i++) {
key = node.toString() + i;
circle.remove(hashFunction.hash(key));
}
}
/**
* 根据对象的key值,映射到hash表中,得到与对象hash值最近的服务器,就是对象待存放的服务器
*
* @param key
* @return
*/
public T get(Object key) {
if (circle.isEmpty()) {
return null;
}
// 得到对象的hash值,根据该hash值找hash值最接近的服务器
String hash = hashFunction.hash((String) key);
// 以下为核心部分,寻找与上面hash最近的hash指向的服务器
// 如果hash表circle中没有该hash值
if (!circle.containsKey(hash)) {
// tailMap为大于该hash值的circle的部分
SortedMap<String, T> tailMap = circle.tailMap(hash);
// tailMap.isEmpty()表示没有大于该hash的hash值
// 如果没有大于该hash的hash值,那么从circle头开始找第一个;如果有大于该hash值得hash,那么就是第一个大于该hash值的hash为服务器
// 既逻辑上构成一个环,如果达到最后,则从头开始
hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
}
return circle.get(hash);
}
/**
* 测试
*
* @param args
*/
public static void main(String[] args) {
// 定义几个服务器的名称,存放到集合中
Collection<String> nodes = new HashSet<String>();
nodes.add("192.0.0.1");
nodes.add("192.0.0.2");
nodes.add("192.0.0.3");
nodes.add("192.0.0.4");
nodes.add("192.0.0.5");
nodes.add("192.0.0.6");
// MD5压缩算法实现的hash函数
HashFunction hashFunction = new HashFunction();
ConsistentHash<String> cHash = new ConsistentHash<String>(hashFunction,
4, nodes);
// 对象的key值为"google_baidu"
String key[] = { "google", "163", "baidu", "sina" };
// 利用一致性哈希,得到该对象应该存放的服务器
String server[] = new String[key.length];
for (int i = 0; i < key.length; i++) {
server[i] = cHash.get(key[i]);
System.out.println("对象 " + key[i] + " 存放于服务器: " + server[i]);
}
}
}