一致性hash算法

1、一致性hash算法不使用虛擬結點

package algorithm;

import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 
 */
public class ConsistentHashingWithoutVirtualNode {
    /**
     * 待加入Hash環的服務器列表
     */
    private static String[] servers = {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111",
            "192.168.0.3:111", "192.168.0.4:111"};

    /**
     * key表示服務器的hash值,value表示服務器的名稱
     */
    private static SortedMap<Integer, String> sortedMap = new TreeMap<>();

    /**
     * 程序初始化,將所有的服務器放入到sortedMap中
     */
    static {
        for (int i = 0; i < servers.length; i++) {
            int hash = getHash(servers[i]);
            System.out.println("[" + servers[i] + "]加入集合中, 其Hash值爲" + hash);
            sortedMap.put(hash, servers[i]);
        }
    }

    /**
     * 使用FNV1_32_Hash算法計算服務器的hash值,,這裏不需要重寫hashCode的方法,最終效果沒有區別
     */
    public static int getHash(String str) {
        final int p = 167777619;
        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;
    }

    /**
     * 得到應當路由到的結點
     */
    private static String getServer(String node) {
        //得到帶路由的結點的hash值
        int hash = getHash(node);
        //得到大於該Hash值的所有Map
        SortedMap<Integer, String> subMap = sortedMap.tailMap(hash);
        //第一個Key就是順時針過去離node最近的那個節點
        Integer i = subMap.firstKey();
        //返回對應的服務器名稱
        return subMap.get(i);
    }

    /**
     * 測試
     */
    public static void main(String[] args) {
        String[] nodes = {"127.0.0.1:1111", "221.226.0.1:2222", "10.211.0.1:3333"};
        for (int i = 0; i < nodes.length; i++) {
            System.out.println("[" + nodes[i] + "]的hash值爲" + getHash(nodes[i]) + ",被路由到的結點[" + getServer(nodes[i]) + "]");
        }
    }
}

2、一致性hash算法使用虛擬結點

package algorithm;

import java.util.*;


public class ConsistentHashingWithVirtualNode {
    /**
     * 待加入Hash環的服務器列表
     */
    private static String[] servers = {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111"};

    /**
     * 真實結點列表,考慮到服務器上線、下線的場景,即添加、刪除的場景會比較頻繁,這裏使用LinkList比較好
     */
    private static List<String> realNodes = new LinkedList<>();

    /**
     * 虛擬結點,key表示虛擬結點的hash值,value表示虛擬結點的名稱
     */
    private static SortedMap<Integer, String> virtualNodes = new TreeMap<>();

    /**
     * 虛擬結點的數目,這裏寫死,爲了演示需要,一個真實結點對應5個虛擬結點
     */
    private static final int VIRTUAL_NODES_NUM = 1000;

    static {
        //先把原始的服務器添加到真實結點列表中
        for (int i = 0; i < servers.length; i++) {
            realNodes.add(servers[i]);
        }
        //在添加虛擬結點,遍歷LinkedList使用foreach效率會比較高
        for (String str : realNodes) {
            for (int i = 0; i < VIRTUAL_NODES_NUM; i++) {
                String virtualNodeName = str + "&&VN" + i;
                int hash = getHash(virtualNodeName);
                System.out.println("虛擬節點[" + virtualNodeName + "]被添加, hash值爲" + hash);
                virtualNodes.put(hash, virtualNodeName);
            }
        }
    }

    /**
     * 使用FNV1_32_Hash算法計算服務器的hash值,,這裏不需要重寫hashCode的方法,最終效果沒有區別
     */
    public static int getHash(String str) {
        final int p = 167777619;
        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;
    }

    /**
     * 得到應當路由到的結點
     */
    private static String getServer(String node) {
        //得到帶路由的結點的hash值
        int hash = getHash(node);
        //得到大於該Hash值的所有map
        SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
        Integer i = null;
        String virtualNode = null;
        if (subMap == null || subMap.size() == 0) {
            //取第一個結點
            i = virtualNodes.firstKey();
            virtualNode = virtualNodes.get(i);
        } else {
            i = subMap.firstKey();
            virtualNode = virtualNodes.get(i);
        }
        //返回真實結點地址
        return virtualNode.substring(0, virtualNode.indexOf("&&"));
    }

    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        ArrayList<String> id = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            id.add(UUID.randomUUID().toString().replace("-", ""));
        }
        for (int i = 0; i < id.size(); i++) {
            String aString = getServer(id.get(i));
            Integer aInteger = map.get(aString);
            if (aInteger == null) {
                map.put(aString, 1);
            } else {
                map.put(aString, aInteger + 1);
            }
            Set<String> set = map.keySet();
            for (String a : set) {
                System.out.println("節點【" + a + "】分配到元素個數爲==>" + map.get(a));
            }
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章