import java.util.*;
/**
* 一致性hash算法
* 1.物理节点
* 2.虚拟节点
* 3.hash算法
* 4.虚拟节点放到环上
* 5.数据找到对应的虚拟节点 找到虚拟节点 就相当于找到了物理节点
* */
public class BigTreeConsistenceHash {
//物理节点 相当于集群的服务器 对象(id,name,ip...) 此处简化 用String 代表
private List<String> realNodes = new ArrayList<>();
//保存物理节点与虚拟节点的关系
private Map<String,List<Integer>> realToVirMap = new HashMap<>();
//定义虚拟节点的数量 物理节点下定义多个虚拟节点 保证虚拟节点均衡分布在hash环上 保证数据均匀分布在虚拟节点内 避免数据偏移
private int virutalNums = 100 ;
//排序存储结构 环 key虚拟节点hash值,value物理节点名 SortedMap根据key值排序的map
private SortedMap<Integer,String> sortedMap= new TreeMap<>();
public BigTreeConsistenceHash(int virutalNums) {
this.virutalNums = virutalNums;
}
/**
* 定义添加物理节点
* node 物理节点
* */
public void addServer(String node){
this.realNodes.add(node);
//虚拟出指定数量的虚拟节点
int count = 0, i = 0;
String vnode = null; //虚拟节点名称
List<Integer> virutalNodes = new ArrayList<>(this.virutalNums); //虚拟节点list
while(count<virutalNums){
i++;
vnode = node+"--"+i;
//获取虚拟节点hash值
//hash算法 hashCode 不够散列 有负值(取绝对值即可)
//其他hash算法 FNV1_32_HASH 等
int hashValue = FNV1_32_HASH.getHash(vnode);
//判断这个hash值的节点是否已经存在 如果不能存在继续执行 否则 重新生成
if(!sortedMap.containsKey(hashValue)){
//虚拟节点list添加
virutalNodes.add(hashValue);
//虚拟节点放到环上 数据结构:树 红黑树
this.sortedMap.put(hashValue,node);
count++;
}
}
//保存物理节点与虚拟节点的对应关系
this.realToVirMap.put(node,virutalNodes);
}
/**
* 缓存数据插入 确定数据放入哪个物理节点
* */
public String getServer(String key){
int hashValue = FNV1_32_HASH.getHash(key);
//顺时针方向找 离他最近的虚拟节点的hash值 tailMap 方法 获取key值>=传参值的 所有对象
SortedMap<Integer,String> subMap = sortedMap.tailMap(hashValue);
//如果是空的 说明hash值比最后一个虚拟节点大 数据应分配给第一个虚拟节点 所归属的服务器
if(subMap.isEmpty()){
return sortedMap.get(sortedMap.firstKey());
}else{
return subMap.get(subMap.firstKey());
}
}
/**
* 移除物理节点
* */
public void removeService(String node){
List<Integer> virList = this.realToVirMap.get(node);
for(Integer hashKey:virList){
sortedMap.remove(hashKey);
}
}
public static void main(String[] args) {
BigTreeConsistenceHash ch = new BigTreeConsistenceHash(100);
ch.addServer("192.168.10.10");
ch.addServer("192.168.10.11");
ch.addServer("192.168.10.12");
//模拟数据测试
for(int i=0;i<10;i++){
String dataKey = "data-"+i;
System.out.println(dataKey+"对应的服务器是"+ch.getServer(dataKey));
}
}
}
FNV1_32_HASH算法源码
public class FNV1_32_HASH {
public static int getHash(String str)
{
final int p = 16777619;
int hash = (int)2166136261L;
for (int i = 0; i < str.length(); i++)
hash = (hash ^ str.charAt(i)) * p;
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
// 如果算出来的值为负数则取其绝对值
if (hash < 0)
hash = Math.abs(hash);
return hash;
}
}