Jackiesteed

www.github.com/jackiesteed

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
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"));


    }
}

 

posted on 2015-05-24 23:47  Jackiesteed  阅读(169)  评论(0编辑  收藏  举报