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")); } }