一致性哈希JAVA實現

一致性哈希算法JAVA實現

最近在工做中,因爲接手了JAVA相關的項目代碼,涉及到資源分配的相關知識,裏面用到了一致性哈希進行負載均衡,因而就趁這個機會學習了下一致性哈希的相關內容,在此作一下學習總結。node

一致性哈希介紹

一致性哈希的主要做用就是用來進行負載均衡,咱們設想這麼一個場景,咱們有四臺服務器,而後須要將一批資源均勻的分配到這四臺服務器上。算法

不含虛擬節點的一致性哈希

首先咱們先想象有這麼一個圓環(也叫哈希環),在哈希環上有2^32個節點;服務器

一、對服務器(能惟一標識該服務的屬性,好比服務器的IP)進行求哈希值,使其落到哈希環,如圖所示Node0-Node3(四個箭頭所示)對應四臺服務器;負載均衡

二、對咱們要分配的資源進行求哈希值,一樣落到哈希環上,如圖所示(圖中的星標標識落在哈希環上的資源);函數

三、規定落在哈希環上的資源,分配到該資源所在節點順時針遇到的第一個服務節點上,如圖所示;
image-20200530205208550.png工具

若是服務器所在的哈希環上的節點如上圖所示,均勻分佈,那天然是最好的,但若是像下圖所示,那麼就會致使資源的分配不均。學習

image-20200530213610329.png

爲了解決這種狀況出現的問題,出現了虛擬節點的一致性哈希。spa

虛擬節點的一致性哈希

所謂的虛擬節點,就是咱們假想出來的節點,將這些虛擬節點映射到哈希環上,而後將這些虛擬節點與實際服務器進行關聯,例如虛擬節點Node0,Node1與服務器1關聯,那麼分配到Node0,Node1的資源,也便是分配到實際的服務器1上。code

也就是說咱們實際的服務器有四臺,但咱們能夠將N個虛擬的節點映射到哈希環上,這樣就能經過調節虛擬節點與實際服務器的關聯關係,來達到調節分配到不一樣服務器資源的數量的目的。server

一致性哈希的代碼實現

/**
 * 計算哈希值的工具類
 */
public class Hash {

    /**
     * 使用String自帶的hashCode函數
     * @param str
     * @return
     */
    public static Integer stringHashcode(String str){
        int hash = str.hashCode();
        if (hash<0){
            hash = Math.abs(hash);
        }
        return hash;
    }

    /**
     * 使用FNV1_32_HASH算法計算hash值
     * @param str
     * @return
     */
    public static Integer FNV1_32_HASH(String str){
        final int p = 1677769;

        int hash = (int)0;
        int len = str.length();
        for (int i=0;i<len;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;
    }
}
不含虛擬節點的實現
/**
 * 不帶虛擬節點的一致性哈希實現
 */
public class HashTest {
    private static String[] servers = {"192.168.0.1:8999","192.168.0.2:8999","192.168.0.0.3:8999"};

    /**
     * key 服務器的哈希值、
     * value 服務器信息
     */
    private static SortedMap<Integer,String> map = new TreeMap<Integer, String>();

    static {
        /**
         * 將服務器信息存放到map中
         */
        int size = servers.length;
        for (int i = 0;i<size;i++){
            int hash = Hash.FNV1_32_HASH(servers[i]);
            map.put(hash, servers[i]);
            System.out.println("hash="+hash+"  "+"server="+servers[i]);
        }
    }


    /**
     * 獲取資源所在的節點服務器
     * @param node
     * @return
     */
    public static String getServer(String node){

        Integer hash = Hash.FNV1_32_HASH(node);

        //獲取大於當前node的hash值的全部服務節點
        SortedMap<Integer, String> sortedMap = map.tailMap(hash);

        Integer key = 0;
        if (sortedMap.isEmpty()){
            //若是沒有比node節點hash值大的服務節點,則取hash值最小的服務節點
            key = map.firstKey();
        }else{
            //獲取過濾出的服務節點的第一個值,也就是從node開始順時針遇到的第一個服務節點
             key = sortedMap.firstKey();
        }
        String server = map.get(key);
        return server;
    }

    public static void main(String[] args) {
        String[] index = {"582639584","582639585","582639586","582639587"};
        System.out.println("-------------");
        int size = index.length;
        for(int i=0;i<size;i++){
            String server = getServer(index[i]);
            System.out.println("index="+index[i] +" hash="+Hash.FNV1_32_HASH(index[i]) +" server="+server);
        }
    }

}

運行結果:

hash=474459784  server=192.168.0.1:8999
hash=1451861892  server=192.168.0.2:8999
hash=1078738565  server=192.168.0.0.3:8999
-------------
index=582639584 hash=1309097570 server=192.168.0.2:8999
index=582639585 hash=583335179 server=192.168.0.0.3:8999
index=582639586 hash=1918263339 server=192.168.0.1:8999
index=582639587 hash=1873142797 server=192.168.0.1:8999
帶虛擬節點的代碼實現
/**
 * 帶虛擬節點的代碼實現
 */
public class HashVirtualNodeTest {
    /**
     * 實際的服務器
     */
    private static String[] servers = {"192.168.0.1:8999","192.168.0.2:8999","192.168.0.0.3:8999"};

    /**
     *每一個服務節點對應的虛擬服務節點數目
     */
    private final static int virtualNode = 5;

    /**
     * 哈希環上的虛擬服務節點
     * key 虛擬服務節點的hash值
     * value 虛擬服務節點名稱
     */
    private static SortedMap<Integer,String> virtualNodeMap = new TreeMap<Integer, String>();

    static{
        /**
         * 將虛擬節點放到virtualNodeMap中
         * 虛擬節點與實際的服務器的對應關係
         * 服務器名稱:server
         * 虛擬節點名:server&&Node0 server&&Node1 server&&Node2 ...
         */
        int len = servers.length;
        for (int i=0;i<len;i++){
            for(int j=0;j<virtualNode;j++){
                String virtualNodeName = servers[i]+"&&"+"Node"+j;
                Integer hash = Hash.FNV1_32_HASH(virtualNodeName);
                virtualNodeMap.put(hash,virtualNodeName);
                System.out.println("hash="+hash+" virtualNode=" +virtualNodeName);
            }
        }
    }

    /**
     * 獲取一個節點對應的分配到的服務節點
     * @param node 待查詢的節點
     * @return 實際的服務節點名稱
     */
    public static String getServer(String node){
        Integer hash = Hash.FNV1_32_HASH(node);

        //獲取大於當前節點的虛擬節點服務器
        SortedMap<Integer, String> tailMap = virtualNodeMap.tailMap(hash);

        Integer key = 0;
        if(virtualNodeMap.isEmpty()){
            //沒有大於當前節點的虛擬服務節點時,取虛擬服務節點的第一個
            key = virtualNodeMap.firstKey();
        }else{
            //取出大於當前節點的第一個虛擬服務節點
            key = tailMap.firstKey();
        }
        //取出對應的虛擬節點
        String virtualNode = virtualNodeMap.get(key);

        //獲取實際的服務名稱
        String server = virtualNode.split("&&")[0];
        return server;
    }


    public static void main(String[] args){
        String[] index = {"582639584","582639585","582639586","582639587","582639588"};

        System.out.println("------------------");
        int size = index.length;
        for (int i=0;i<size;i++){
            String server = getServer(index[i]);
            System.out.println("index="+index[i]+" hash="+ Hash.FNV1_32_HASH(index[i]) +" server="+server);
        }
    }
}

運行結果:

hash=350621948 virtualNode=192.168.0.1:8999&&Node0
hash=2008701201 virtualNode=192.168.0.1:8999&&Node1
hash=399766123 virtualNode=192.168.0.1:8999&&Node2
hash=9572053 virtualNode=192.168.0.1:8999&&Node3
hash=310503051 virtualNode=192.168.0.1:8999&&Node4
hash=102010718 virtualNode=192.168.0.2:8999&&Node0
hash=1966288165 virtualNode=192.168.0.2:8999&&Node1
hash=1935085864 virtualNode=192.168.0.2:8999&&Node2
hash=2058939177 virtualNode=192.168.0.2:8999&&Node3
hash=1101158742 virtualNode=192.168.0.2:8999&&Node4
hash=1042841091 virtualNode=192.168.0.0.3:8999&&Node0
hash=1013102794 virtualNode=192.168.0.0.3:8999&&Node1
hash=1097523199 virtualNode=192.168.0.0.3:8999&&Node2
hash=384605107 virtualNode=192.168.0.0.3:8999&&Node3
hash=1615270737 virtualNode=192.168.0.0.3:8999&&Node4
------------------
index=582639584 hash=1309097570 server=192.168.0.0.3:8999
index=582639585 hash=583335179 server=192.168.0.0.3:8999
index=582639586 hash=1918263339 server=192.168.0.2:8999
index=582639587 hash=1873142797 server=192.168.0.2:8999
index=582639588 hash=1040353541 server=192.168.0.0.3:8999
相關文章
相關標籤/搜索