一致性hash算法通常在分佈式系統中運用比較普遍,例如多節點的數據存儲,dubbo框架中也實現了一致性hash算法來選擇服務提供者。還有在mysql集羣,分庫分表時,一致性hash也是比較經常使用的用來劃分數據區域的算法。java
舉個例子,電商公司的訂單表,通常都會用數據庫集羣或者分庫分表,用多個數據庫實例在承載訂單信息,這麼大數據量的信息,在查詢的時候,首先必需要保證冪等性,就是屢次調用hash算法得出的hash值都必須同樣,這樣才能保證在去查詢訂單信息的時候,都會到對應的數據庫實例中去查詢,不然就可能會查不到訂單信息,以前經歷的就是將訂單表拆分爲了512張表,按照訂單時間來經行hash一致性算法node
一致性hash的數據結構:mysql
藍色表示機器節點,橘黃色表示數據節點算法
真實節點:A B C Dsql
虛擬節點:A1 A2 B1 B2 C1 C2 D1 D2數據庫
數據存放規則:計算出各個虛擬節點的hash值(不必定非要hash算法,能夠是符合本身業務邏輯的算法,只要保證有效的數據經過hash算法可以在hash環內都有落點而且具備冪等性),例如:K1的hash值∈(A1,C2),則得出K1應該存放在C2節點數據結構
本章用java實現了兩種hash一致性算法;框架
1.沒有虛擬節點的hash環(邏輯環)分佈式
2.帶虛擬節點的hash環(邏輯環)測試
備註:我這裏爲了方便測試,直接給對應的真是節點的hash值寫死了
實現一:
package com.dubbo.algorithm; import java.util.SortedMap; import java.util.TreeMap; public class ShardHash { //真實機器節點 <hash值,節點信息> private TreeMap<Long,String> realNodes = new TreeMap<Long,String>(); public ShardHash(){ init(); } public void init(){ // A B C D 4個真實節點 realNodes.put(1L, "A"); realNodes.put(6L, "B"); realNodes.put(14L, "C"); realNodes.put(25L, "D"); /* realNodes.put(hash("A"), "A"); realNodes.put(hash("B"), "B"); realNodes.put(hash("C"), "C"); realNodes.put(hash("D"), "D"); */ } public String getNode(String key){ //方便測試 Long keyHash = Long.valueOf(key); // Long keyHash = hash(key); //根據key的hash值,找到hash環上面比此hash值大的節點 SortedMap<Long,String> sorted = realNodes.tailMap(keyHash); if(sorted.isEmpty()){ //沒有比key的hash大的節點,則該key存放在第一個節點上面 String node = realNodes.get(realNodes.firstKey()); return node; } String node = sorted.get(sorted.firstKey()); return node; } /** * 網上copy的一個hash算法,能夠選擇適合本身場景的hash算法 */ private Long hash(String str) { final int p = 16777619; Long hash = 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; } public static void main(String[] args) { ShardHash s = new ShardHash(); String node = s.getNode("33"); System.out.println("數據存放節點爲:"+node); } }
實現二:
package com.dubbo.algorithm; import java.util.LinkedList; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; public class ShardHashV { //真實節點,4個真實節點 private List<String> realNodes = new LinkedList<String>(); //虛擬節點 private TreeMap<Long,String> vNode = new TreeMap<Long,String>(); //每一個真實節點對應多少個虛擬節點 private int VNODE_NUM = 4; //區間大小 private static Long count = 0L; public ShardHashV(){ init(); } //0--5--10--15.... public void init(){ realNodes.add("A1"); realNodes.add("B1"); realNodes.add("C1"); realNodes.add("D1"); for(int i=0;i<realNodes.size();i++){ for(int j=0;j<VNODE_NUM;j++){ vNode.put(hash(), "A"+(j+1)); vNode.put(hash(), "B"+(j+1)); vNode.put(hash(), "C"+(j+1)); vNode.put(hash(), "D"+(j+1)); } } } public String getNodes(String key){ //找到全部比key大的節點 SortedMap<Long,String> sm = vNode.tailMap(Long.valueOf(key)); if(sm.isEmpty()){ String node =vNode.get(vNode.firstKey()) ; return node; } String node =vNode.get(sm.firstKey()) ; return node; } public void remove(String key){ vNode.remove(Long.valueOf(key)); } public static void main(String[] args) { ShardHashV sv = new ShardHashV(); System.out.println(sv.getNodes("0")); System.out.println(sv.getNodes("7")); System.out.println(sv.getNodes("11")); System.out.println(sv.getNodes("16")); System.out.println(sv.getNodes("54")); //刪除B1節點 sv.remove("10"); System.out.println(sv.getNodes("7")); sv.remove("5"); System.out.println(sv.getNodes("3")); } /** * 網上copy的一個hash算法,能夠選擇適合本身場景的hash算法 */ private Long hash() { Long key = this.count+5L; count=count+5L; return key; } }