package com.jackiesteed.algorithms;
import java.util.*;
/**
* 虚拟节点个数需要事先确定好, 而且不能修改.
* 核心代码模拟了一下remapping的逻辑, 如何做到均衡分配.
* 如何保证一致性呢? 每次重新分配的时候, 只有被移动的虚拟节点才会收到影响, 保持了一致性.
* 一致性hash 简单实现, 单线程使用
* key 固定为String, value 支持泛型
* 感觉只是走了下流程, 离实际使用在性能, 并发方面还要改进.
* Created by jackie on 5/24/15.
*/
public class ConsistentHashMap <T>{
/**
* 存储机器id对应的真实ip, 用来做远程调用.
*/
private Map<String, Stack<Integer>> realNodeMap;
/**
* 虚拟节点到真实节点的映射
* 虚拟节点数根据现实情况来权衡.
* 节点数极大的话, 可以保证趋向于决定均匀分布, 但是remapping的时间开销交大.
* 节点数少, 压力不一定非常均匀, 但是remapping速度回块一些.
* 但是虚拟节点数, 一定要是服务所需的机器数的N倍之多.
*/
private String[] virtualRealMap;
/**
* 用来模拟分布式系统里面的每台机器
* key 是ip, value mock机器里面的存储, 是个java.util.Map
*/
private Map<String, Map<String, T>> hostMock;
/**
* 构造函数里面初始化虚拟节点个数.
* @param virtualNodeCount
*/
public ConsistentHashMap(int virtualNodeCount){
virtualRealMap = new String[virtualNodeCount];
realNodeMap = new HashMap<String, Stack<Integer>>();
hostMock = new HashMap<String, Map<String, T>>();
}
/**
* 对外接口, 写入数据到分布式存储里面.
* @param key
* @param value
*/
public void put(String key, T value){
put(getRealHashCode(key), key, value);
}
/**
* 对外暴露的接口, 从分布式存储提取数据.
* @param key
* @return
*/
public T get(String key){
return get(getRealHashCode(key), key);
}
/**
* 根据key计算需要映射的真是机器的id.
* @param key
* @return
*/
private String getRealHashCode(String key){
//字符串原本的hash值
int oriHashCode = key.hashCode();
//key对应的虚拟节点的hash值
int virtualHashCode = (oriHashCode % virtualRealMap.length + virtualRealMap.length) % virtualRealMap.length;
//映射出来的真实
String ip = virtualRealMap[virtualHashCode];
return ip;
}
/**
* 这个其实就是和真是的数据存储进行交互了, ip用来访问真实机器.
* @param ip
* @param key
* @param value
*/
private void put(String ip, String key, T value){
hostMock.get(ip).put(key, value);
}
/**
* realHashCode表示对应机器的id, 从这台机器上面获取数据.
* @param
* @param key
* @return
*/
private T get(String ip, String key){
return hostMock.get(ip).get(key);
}
/**
* 添加一台机器.
* @param ip
*/
public void addHost(String ip){
/**
* 如果已经存在了, 就不加入
*/
if(realNodeMap.containsKey(ip))
return;
hostMock.put(ip, new HashMap<String, T>());
//目前还没有机器加入, 那么需要走特殊的初始化流程
if(realNodeMap.isEmpty()){
Stack<Integer> virtualIds = new Stack<Integer>();
for(int i = 0; i < virtualRealMap.length; i++){
virtualRealMap[i] = ip;
virtualIds.push(i);
}
realNodeMap.put(ip, virtualIds);
return;
}
int expectCount = virtualRealMap.length / (realNodeMap.size() + 1);
Stack<Integer> virtualIds = new Stack<Integer>();
while(virtualIds.size() < expectCount){
for(Map.Entry<String, Stack<Integer>> entry : realNodeMap.entrySet()){
Stack<Integer> oldVirtualIds = entry.getValue();
while(oldVirtualIds.size() > expectCount){
int virtualId = oldVirtualIds.pop();
virtualRealMap[virtualId] = ip;
virtualIds.push(virtualId);
}
if(virtualIds.size() >= expectCount)
break;
}
}
realNodeMap.put(ip, virtualIds);
}
/**
* 从分布式系统里删除一台机器
* @param ip
*/
public void deleteHost(String ip){
if(!realNodeMap.containsKey(ip))
return;
hostMock.remove(ip);
Stack<Integer> virtualIds = realNodeMap.get(ip);
int expectCount = virtualRealMap.length / (realNodeMap.size() - 1);
realNodeMap.remove(ip);
//说明这是最后一个ip, 没有办法把这个ip映射的虚拟节点重新映射了, 直接全部都删掉.
if(realNodeMap.isEmpty()){
for(int i = 0; i < virtualRealMap.length; i++){
virtualRealMap[i] = null;
}
return;
}
while(!virtualIds.isEmpty()){
for(Map.Entry<String, Stack<Integer>> entry : realNodeMap.entrySet()){
Stack<Integer> oldVirtualIds = entry.getValue();
while(oldVirtualIds.size() < expectCount && !virtualIds.isEmpty()){
int virtualId = virtualIds.pop();
virtualRealMap[virtualId] = ip;
oldVirtualIds.push(virtualId);
}
if(virtualIds.isEmpty())
break;
}
//如果不增加, 有可能永远分配不出去.
expectCount++;
}
}
public void dump(){
System.out.println("=====================================================");
System.out.println("VirtualNodeCount : " + virtualRealMap.length);
for(Map.Entry<String, Stack<Integer>> entry : realNodeMap.entrySet()){
System.out.print(entry.getKey() + " : | ");
for(Integer virtualId : entry.getValue()){
System.out.format("%2d | ", virtualId);
}
System.out.println();
}
System.out.println("=====================================================");
}
public static void main(String[] args){
ConsistentHashMap<String> consistentHashMap = new ConsistentHashMap<String>(30);
consistentHashMap.addHost("192.168.0.1");
consistentHashMap.addHost("192.168.0.2");
consistentHashMap.addHost("192.168.0.4");
consistentHashMap.addHost("192.168.0.3");
consistentHashMap.addHost("192.168.0.8");
consistentHashMap.addHost("192.168.0.13");
consistentHashMap.addHost("192.168.0.14");
consistentHashMap.deleteHost("192.168.0.4");
consistentHashMap.deleteHost("192.168.0.1");
consistentHashMap.deleteHost("192.168.0.2");
consistentHashMap.addHost("123.123.123.123");
consistentHashMap.addHost("123.123.123.124");
consistentHashMap.addHost("123.123.123.125");
consistentHashMap.dump();
consistentHashMap.put("jackie", "abc");
System.out.println(consistentHashMap.get("jackie"));
}
}