一致性hash

一致性Hash的應(yīng)用場景

我們先假設(shè)一種場景,有一個應(yīng)用的請求量非常大,在服務(wù)端有若干臺緩存服務(wù)器,這些緩存服務(wù)器記錄了各用戶請求的緩存數(shù)據(jù)(當(dāng)然這樣做的目的我想大家應(yīng)該清楚)。怎樣將這么多的請求按照一定的規(guī)則分發(fā)到不同的緩存服務(wù)器就顯得非常關(guān)鍵。

一種簡單的調(diào)度規(guī)則是通過求余的方式將請求關(guān)聯(lián)到某一臺服務(wù)器,比如說有10臺服務(wù)器,那么通過請求的hash值對10求余就關(guān)聯(lián)到了其中的一臺服務(wù)器??雌饋磉@種方法還ok,但是運行中的服務(wù)器難免有掛掉的風(fēng)險,或者任務(wù)分發(fā)后服務(wù)器的負載仍然很大致使我們不得不增加服務(wù)器,遇到這樣類似的情況,如果我們還是通過上述方法進行調(diào)度的話就會打亂原來的請求映射,比如請求A之前關(guān)聯(lián)的是服務(wù)器1001,當(dāng)服務(wù)器數(shù)量發(fā)生改變之后,通過求余,很可能A就關(guān)聯(lián)到了其他的服務(wù)器,而這是我們不能接受的,我們希望的是服務(wù)器的增減盡量不影響用戶之前的緩存映射。怎么辦?一致性Hash就能夠較好的解決這個問題。

一致性Hash的原理

整體來看,有兩個基本空間,用戶空間和服務(wù)空間,我們的目的是將這兩個空間關(guān)聯(lián)起來,這里我們引入hash空間,將用戶空間和服務(wù)空間都映射到hash空間,這樣兩者就關(guān)聯(lián)起來了。將服務(wù)器映射到hash空間,按照hash值由小到大順時針排列,并且首尾相連,形成一個環(huán)形。當(dāng)用戶請求過來的時候,也將其映射到hash空間上,然后將其關(guān)聯(lián)到順時針最近的服務(wù)器即可。但有一個問題是,比如說當(dāng)其中一個服務(wù)器節(jié)掛掉時,按照這個規(guī)則,盡管之前關(guān)聯(lián)到其他服務(wù)器的請求的關(guān)聯(lián)性不會發(fā)生改變,但關(guān)聯(lián)到該服務(wù)器的請求就全部關(guān)聯(lián)到了另一臺服務(wù)器。這樣勢必會增大后者的負載,不甚合理。
換一種方式,現(xiàn)在我們不直接將服務(wù)器映射到hash空間,而是基于每一臺服務(wù)器虛擬出諸多虛擬服務(wù)器(100臺或者更多),我們將這些虛擬服務(wù)器映射到hash空間,這樣歸屬不同服務(wù)器的各虛擬服務(wù)器就交織在hash空間中,當(dāng)請求過來時先關(guān)聯(lián)到某個虛擬服務(wù)器,然后再將虛擬服務(wù)器關(guān)聯(lián)到其所屬的真實服務(wù)器。當(dāng)其中的一臺服務(wù)器掛掉時,之前歸屬到這臺服務(wù)器的請求就可能關(guān)聯(lián)到其他任意服務(wù)器,這樣就實現(xiàn)了負載均衡,即所謂的一致性hash。

代碼示例

首先定義服務(wù)器節(jié)點:

    public static class Node{
        String name;
        String ip;
        public Node(String name, String ip){
            this.name = name;
            this.ip = ip;
        }
        public String toString(){
            return this.name+"-"+this.ip;
        }
    }

然后是測試類:(這里有兩個關(guān)鍵點,一個是hash環(huán)的管理,另一個是hash函數(shù)的選取。本實例用的是murmur哈希)

    public class Shard<Node> {
        private static TreeMap<Long, Node> nodes;//虛擬主機hash環(huán)
        private static TreeMap<Long, Node> treeKey;//客戶機主機映射
        private List<Node> mNodes = new ArrayList<Node>();//實體主機列表
        private final Integer vNodes = 100;//一個實體對應(yīng)虛擬主機個數(shù)
        public Shard(List<Node> mNodes){
            this.mNodes = mNodes;
            init();
        }
        //main函數(shù)
        public static void main(String[] args){
            Node n1 = new Node("n1", "172.16.226.111");
            Node n2 = new Node("n2", "172.16.226.112");
            Node n3 = new Node("n3", "172.16.226.113");
            Node n4 = new Node("n4", "172.16.226.114");
            List<Node> nodes = new ArrayList<Node>();
            nodes.add(n1);
            nodes.add(n2);
            nodes.add(n3);
            nodes.add(n4);
            Shard<Node> shardN = new Shard<Node>(nodes);
            System.out.println("添加客戶端,一開始有4個主機,分別為n1,n2,n3,n4,每個主機有100個虛擬主機:");
            shardN.keyToNode("1111號客戶端");
            shardN.keyToNode("1112號客戶端");
            shardN.keyToNode("1113號客戶端");
            shardN.keyToNode("1114號客戶端");
            shardN.keyToNode("1115號客戶端");
            shardN.keyToNode("1116號客戶端");
            shardN.keyToNode("1117號客戶端");
            shardN.keyToNode("1118號客戶端");
            shardN.keyToNode("1119號客戶端");
            shardN.deleteNode(n2);//刪除節(jié)點n2
            Node n5 = new Node("n3", "172.16.226.115");
            shardN.addNode(n5);//增加節(jié)點n5
        }
        //構(gòu)建hash環(huán)
        public void init (){
            nodes = new TreeMap<Long, Node>();
            treeKey = new TreeMap<Long, Node>();
            for(Node n : mNodes){
                for(Integer i=0;i<vNodes;i++){
                    nodes.put(hash("SHARD-"+n.name+"-NODE-"+i), n);
                }
            }
        }
        //刪除節(jié)點
        public  void deleteNode(Node node){
            System.out.println("刪除節(jié)點:"+node);
            for(int i=0;i<vNodes;i++){
                Long key = hash("SHARD-"+node.name+"-NODE-"+i);
                //nodes.remove(key);
                SortedMap<Long,Node> headNodes = nodes.headMap(key);
                SortedMap<Long,Node> tailNodes = nodes.tailMap(key);
                SortedMap<Long,Node> betweenNodes;
                if(headNodes.size() == 0){
                    betweenNodes = treeKey.tailMap(tailNodes.lastKey());
                }else{
                    betweenNodes = treeKey.subMap(headNodes.lastKey(), key);
                }
                nodes.remove(tailNodes.firstKey());
                tailNodes.remove(tailNodes.firstKey());
                Iterator iterator = betweenNodes.keySet().iterator();
                while(iterator.hasNext()){
                    Long keytemp = (Long)iterator.next();
                    if(tailNodes.size() == 0){
                        treeKey.put(keytemp, headNodes.get(headNodes.firstKey()));
                        System.out.println(keytemp + "連接轉(zhuǎn)移到->" + headNodes.get(headNodes.firstKey()));
                    }else{
                        treeKey.put(keytemp, tailNodes.get(tailNodes.firstKey()));
                        System.out.println(keytemp + "連接轉(zhuǎn)移到->" + tailNodes.get(tailNodes.firstKey()));
                    }
                }
            }
        }
        //增加節(jié)點
        public void addNode(Node node){
            System.out.println("增加節(jié)點:"+node);
            for(int i=0;i<vNodes;i++){
                Long key = hash("SHARD-"+node.name+"-NODE-"+i);
                SortedMap<Long,Node> headNodes = nodes.headMap(key);
                SortedMap<Long,Node> tailNodes = nodes.tailMap(key);
                SortedMap<Long,Node> betweenNodes;
                if(headNodes.size() == 0){
                    betweenNodes = treeKey.tailMap(nodes.lastKey());
                }else{
                    betweenNodes = treeKey.subMap(headNodes.lastKey(), key);
                }
                nodes.put(key, node);
                Iterator iterator = betweenNodes.keySet().iterator();
                while(iterator.hasNext()){
                    Long keytemp = (Long)iterator.next();
                    System.out.println(keytemp + "連接轉(zhuǎn)移到->" + node);
                }
            }
        }
        //將請求關(guān)聯(lián)到服務(wù)器
        public void keyToNode(String key){
            SortedMap<Long, Node> tail = nodes.tailMap(hash(key));
            if(tail.size() == 0){
                treeKey.put(hash(key), nodes.get(nodes.firstKey()));
                System.out.println(key+"(hash:"+hash(key)+")連接到主機->"+nodes.get(nodes.firstKey()));
                return;
            }
            treeKey.put(hash(key), tail.get(tail.firstKey()));
            System.out.println(key+"(hash:"+hash(key)+")連接到主機->"+tail.get(tail.firstKey()));
        }
        //murmur  Hash
        private static Long hash(String key) {  
            ByteBuffer buf = ByteBuffer.wrap(key.getBytes());  
            int seed = 0x1234ABCD;  
            ByteOrder byteOrder = buf.order();  
            buf.order(ByteOrder.LITTLE_ENDIAN);  
            long m = 0xc6a4a7935bd1e995L;  
            int r = 47;  
            long h = seed ^ (buf.remaining() * m);  
            long k;  
            while (buf.remaining() >= 8) {  
                k = buf.getLong();  
                k *= m;  
                k ^= k >>> r;  
                k *= m;  
                h ^= k;  
                h *= m;  
            }  
            if (buf.remaining() > 0) {  
                ByteBuffer finish = ByteBuffer.allocate(8).order(  
                        ByteOrder.LITTLE_ENDIAN);  
                // for big-endian version, do this first:  
                // finish.position(8-buf.remaining());  
                finish.put(buf).rewind();  
                h ^= finish.getLong();  
                h *= m;  
            }  
            h ^= h >>> r;  
            h *= m;  
            h ^= h >>> r;  
            buf.order(byteOrder);  
            return h;  
        }
    }

參考文章

應(yīng)用于負載均衡的一致性哈希及java實現(xiàn)——haitao111313
一致性哈希算法與Java實現(xiàn)
Hash函數(shù)概覽
一致性哈希算法(用于解決服務(wù)器均衡問題)——caigen1988

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容