在工做中常遇到分庫分表或者分佈式結點分佈的問題,保證均衡,可擴展的分配策略很重要,分配策略通常採用hash算法,通常都是餘數等策略,這種hash算法在結點固定的時候會有比較均衡的分佈,可是碰到須要擴展結點就比較難處理,涉及遷移的數據特別多,因此引入一致性哈希算法java
一致性算法能解決什麼問題?node
解決了普通餘數Hash算法伸縮性差的問題,能夠保證在服務器上線或下線變動時儘可能有多的請求命中原來路由到的服務器程序員
算法的具體原理:算法
先構造一個長度爲232的整數環(這個環被稱爲一致性Hash環),根據節點名稱的Hash值(其分佈爲[0, 232-1])將服務器節點放置在這個Hash環上,而後根據數據的Key值計算獲得其Hash值(其分佈也爲[0, 232-1]),接着在Hash環上順時針查找距離這個Key值的Hash值最近的服務器節點,完成Key到服務器的映射查找。服務器
實現:數據結構
一、數據結構選取負載均衡
根據原理咱們須要找到一個合適的數據結構存放232的數據,由於這裏的查詢特別多,因此儘量的選用穩定、查找時間複雜度低的數據結構,這裏採用紅黑樹來存儲(各數據結構的對比參考其餘文檔),咱們主要是採用他的TreeMap方法,由於他自己提供了tailMap(K fromKey)方法,支持從紅黑樹中查找比fromKey大的值的集合,但並不須要遍歷整個數據結構,這樣在集合中取第一個就能實現順時針查找最近結點的需求分佈式
二、Hash算法學習
string的hashcode方法在一致性hash算法中實用價值較低,由於遇到連續的key時hash結果比較集中,作不到均衡,須要從新找個算法計算Hash值,這裏算法比較多好比CRC32_HASH、FNV1_32_HASH、KETAMA_HASH等,這裏實用FNV1_32_HASH算法(其餘hash算法對比參考其餘文檔)ui
三、增長虛擬結點
在原有的負載均衡分佈中增長新的結點,勢必會致使平衡破壞,致使新增結點和該結點後一個結點的請求少於其餘結點,這種能夠增長虛擬結點來解決。原理能夠理解爲對於新增的結點,把他拆分爲多個虛擬結點,而後把這些虛擬結點儘可能分表到hash環上,這樣能夠作到最大程度的解決結點變動致使的負責不均衡問題,可是一個物理結點應該分爲多少個虛擬結點(這個問題待研究)
四、具體代碼實現
1 package com.lexin.hash; 2 3 import java.util.SortedMap; 4 import java.util.TreeMap; 5 6 /** 7 * 8 * 帶虛擬節點的一致性Hash算法 9 * 〈功能詳細描述〉 10 * @author handyliu 11 * @version 2018-2-27 12 * @see ConsistentHashArithmetic 13 * @since 14 */ 15 public class ConsistentHashArithmetic { 16 /** 17 * 物理的服務器列表 18 * 這些列表就是要加入到Hash環的服務器列表 19 */ 20 private static String[] servers = {"192.168.0.0:3306", "192.168.0.1:3306", "192.168.0.2:3306", 21 "192.168.0.3:3306", "192.168.0.4:3306", "192.168.0.5:3306"}; 22 23 24 /** 25 * 虛擬節點使用TreeMap,key表示虛擬節點的hash值,value表示虛擬節點的名稱 26 */ 27 private static SortedMap<Integer, String> virtualNodes = new TreeMap<Integer, String>(); 28 29 /** 30 * 每一個物理結點對應的虛擬節點的數目 31 */ 32 private static final int VIRTUAL_NODES = 5; 33 34 /** 35 * ConsistentHashArithmetic的靜態初始化,把結點放到結點列表中 36 */ 37 static { 38 // 先把原始的服務器添加到真實結點列表中 39 for (int i = 0; i < servers.length; i++){ 40 for (int j = 0; j < VIRTUAL_NODES; j++){ 41 String virtualNodeName = servers[i] + "&Node" + String.valueOf(i); 42 int hashValue = getHashValue(virtualNodeName); 43 virtualNodes.put(hashValue, virtualNodeName); 44 System.out.println("虛擬節點[" + virtualNodeName + "]已添加到hash環, hash值爲:" + hashValue); 45 } 46 } 47 } 48 49 /** 50 * 使用FNV1_32_HASH算法計算Hash值 51 */ 52 private static int getHashValue(String str){ 53 final int p = 16777619; 54 int hash = (int)2166136261L; 55 for (int i = 0; i < str.length(); i++) 56 hash = (hash ^ str.charAt(i)) * p; 57 hash += hash << 13; 58 hash ^= hash >> 7; 59 hash += hash << 3; 60 hash ^= hash >> 17; 61 hash += hash << 5; 62 63 if (hash < 0) 64 hash = Math.abs(hash); 65 return hash; 66 } 67 68 /** 69 * 獲得應當路由到的結點 70 */ 71 private static String getServerNode(String splitKey){ 72 //須要分配的key值 73 int hash = getHashValue(splitKey); 74 // 獲取大於該Hash值的全部Map,直接使用tailMap方法 75 SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash); 76 String virtualNode; 77 if(subMap.isEmpty()){ 78 //若是沒有比該key的hash值大的,則從第一個node開始 79 Integer i = virtualNodes.firstKey(); 80 //返回對應的服務器 81 virtualNode = virtualNodes.get(i); 82 }else{ 83 //第一個Key就是順時針過去離node最近的那個結點 84 Integer i = subMap.firstKey(); 85 //返回對應的服務器 86 virtualNode = subMap.get(i); 87 } 88 //virtualNode虛擬節點名稱要截取一下 89 if(!"".equals(virtualNode)){ 90 return virtualNode.substring(0, virtualNode.indexOf("&")); 91 } 92 return null; 93 } 94 95 public static void main(String[] args){ 96 String[] uids = {"100","199", "10000", "19999", "1000000", "1999999", "100000000", "199999999"}; 97 for (int i = 0; i < uids.length; i++){ 98 System.out.println("[" + uids[i] + "]的hash值爲" + getHashValue(uids[i]) + ", 被路由到結點[" + getServerNode(uids[i]) + "]"); 99 } 100 } 101 }
===================================================
我不能保證寫的每一個地方都是對的,可是至少能保證不復制、不黏貼,保證每一句話、每一行代碼都通過了認真的推敲、仔細的斟酌。每一篇文章的背後,但願都能看到本身對於技術、對於生活的態度。
學習是一種信仰。面對壓力,挑燈夜戰、不眠不休;面對困難,迎難而上、永不退縮。
我是一個純粹的程序員。
===================================================