一致性哈希算法學(xué)習(xí)及JAVA代碼實現(xiàn)分析

1,對于待存儲的海量數(shù)據(jù),如何將它們分配到各個機器中去?---數(shù)據(jù)分片與路由

當(dāng)數(shù)據(jù)量很大時推掸,通過改善單機硬件資源的縱向擴充方式來存儲數(shù)據(jù)變得越來越不適用,而通過增加機器數(shù)目來獲得水平橫向擴展的方式則越來越流行谅畅。因此噪服,就有個問題粘优,如何將這些海量的數(shù)據(jù)分配到各個機器中雹顺?數(shù)據(jù)分布到各個機器存儲之后,又如何進行查找贩挣?這里主要記錄一致性Hash算法如何將數(shù)據(jù)分配到各個機器中去揽惹。

2搪搏,衡量一致性哈希算法好處的四個標(biāo)準(zhǔn):

①平衡性:平衡性是指哈希的結(jié)果能夠盡可能分布到所有的緩沖中去疯溺,這樣可以使得所有的緩沖空間都得到利用囱嫩。②單調(diào)性:單調(diào)性是指如果已經(jīng)有一些數(shù)據(jù)通過哈希分配到了相應(yīng)的機器上漏设,又有新的機器加入到系統(tǒng)中。哈希的結(jié)果應(yīng)能夠保證原有已分配的內(nèi)容可以被映射到原有的或者新的機器中去鸳碧,而不會被映射到舊的機器集合中的其他機器上。

這里再解釋一下:就是原有的數(shù)據(jù)要么還是呆在它所在的機器上不動腾仅,要么被遷移到新的機器上套利,而不會遷移到舊的其他機器上。

③分散性④負載:參考這里:blog.csdn.net/cywosp/article/details/23397179

3验辞,一致性哈希的原理:

由于一般的哈希函數(shù)返回一個int(32bit)型的hashCode受神。因此,可以將該哈希函數(shù)能夠返回的hashCode表示成一個范圍為0---(2^32)-1 環(huán)联四。

將機器的標(biāo)識(如:IP地址)作為哈希函數(shù)的Key映射到環(huán)上朝墩。如:

hash(Node1) =Key1 hash(Node2) = Key2收苏,借用一張圖如下:

image

同樣,數(shù)據(jù)也通過相同的哈希函數(shù)映射到環(huán)上秆乳。這樣屹堰,按照順時針方向肛冶,數(shù)據(jù)存放在它所在的順時針方向上的那個機器上。這就是一致性哈希算法分配數(shù)據(jù)的方式扯键!

4,JAVA實現(xiàn)一致性哈希算法的代碼分析:

?設(shè)計哈希函數(shù)

這里采用了MD5算法荣刑,主要是用來保證平衡性伦乔,即能夠?qū)C器均衡地映射到環(huán)上。貌似用Jdk中String類的hashCode并不能很好的保證平衡性评矩。

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** 實現(xiàn)一致性哈希算法中使用的哈希函數(shù),使用MD5算法來保證一致性哈希的平衡性*/
public class HashFunction {
   private MessageDigest md5 = null;
   public long hash(String key) {
       if (md5 == null) {
           try {
               md5 = MessageDigest.getInstance("MD5");
           } catch (NoSuchAlgorithmException e) { 
              throw new IllegalStateException("no md5 algrithm found");
           } 
      }
       md5.reset();
       md5.update(key.getBytes());
       byte[] bKey = md5.digest();
       //具體的哈希函數(shù)實現(xiàn)細節(jié)--每個字節(jié) & 0xFF 再移位
       long result = ((long) (bKey[3] & 0xFF) << 24) 
              | ((long) (bKey[2] & 0xFF) << 16                      
         | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF)); 
      return result & 0xffffffffL;  
 }}

?實現(xiàn)一致性哈希算法:

為什么要引入虛擬機器節(jié)點?它的作用是什么阱飘?

引入虛擬機器節(jié)點斥杜,其目的就是為了解決數(shù)據(jù)分配不均衡的問題沥匈。因為蔗喂,在將實際的物理機器映射到環(huán)上時缰儿,有可能大部分機器都映射到環(huán)上的某一個部分(比如左半圓上),而通過引入虛擬機器節(jié)點瞪浸,在進行機器hash映射時,不是映射具體機器对蒲,而是映射虛擬機器蹈矮,并保證虛擬機器對應(yīng)的物理機器是均衡的---每臺實際的機器對應(yīng)著相等數(shù)目的Virtual nodes。如下圖:

如何解決集群中添加或者刪除機器上需要遷移大量數(shù)據(jù)的問題鸣驱?

假設(shè)采用傳統(tǒng)的哈希取模法泛鸟,設(shè)有K臺物理機,H(key)=hash(key) mod K 即可實現(xiàn)數(shù)據(jù)分片踊东。但當(dāng)K變化時(如新增一臺機器)谈况,所有已經(jīng)映射好的數(shù)據(jù)都需要重新再映射。H(key)=hash(key) mod (K+1)递胧。

一致性哈希采用的做法如下:引入一個環(huán)的概念碑韵,如上面的第一個圖。先將機器映射到這個環(huán)上缎脾,再將數(shù)據(jù)也通過相同的哈希函數(shù)映射到這個環(huán)上祝闻,數(shù)據(jù)存儲在它順時針走向的那臺機器上。以環(huán)為中介,實現(xiàn)了數(shù)據(jù)與機器數(shù)目之間的解藕联喘。這樣华蜒,當(dāng)機器的數(shù)目變化時,只會影響到增加或刪除的那臺機器所在的環(huán)的鄰接機器的數(shù)據(jù)存儲豁遭,而其他機器上的數(shù)據(jù)不受影響叭喜。

在具體JAVA實現(xiàn)代碼中,定義了一個TreeMap<k, V>用來保存虛擬機器節(jié)點到實際的物理機器的映射蓖谢。機器以字符串形式來標(biāo)識捂蕴,故hash函數(shù)的參數(shù)為String

for (int i = 0; i < numberOfReplicas; i++)   
// 對于一個實際機器節(jié)點 node, 對應(yīng) numberOfReplicas 個虛擬節(jié)點  
    /*
    * 不同的虛擬節(jié)點(i不同)有不同的hash值,但都對應(yīng)同一個實際機器node
    * 虛擬node一般是均衡分布在環(huán)上的,數(shù)據(jù)存儲在順時針方向的虛擬node上
    */
   circle.put(hashFunction.hash(node.toString() + i), node);

而對于 數(shù)據(jù)的存儲而言,邏輯上是按順時針方向存儲在虛擬機器節(jié)點中闪幽,虛擬機器節(jié)點通過TreeMap知道它實際需要將數(shù)據(jù)存儲在哪臺物理機器上啥辨。此外,TreeMap中的Key是有序的盯腌,而環(huán)也是順時針有序的溉知,這樣才能當(dāng)數(shù)據(jù)被映射到兩臺虛擬機器之間的弧上時,通過TreeMap的 tailMap()來尋找順時針方向上的下一臺虛擬機腕够。

if (!circle.containsKey(hash)) {
//數(shù)據(jù)映射在兩臺虛擬機器所在環(huán)之間,就需要按順時針方向?qū)ふ覚C器
   SortedMap<Long, T> tailMap = circle.tailMap(hash);
   hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();}

完整代碼:

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
public class ConsistentHash<T> {
   private final HashFunction hashFunction;
   private final int numberOfReplicas;
   // 節(jié)點的復(fù)制因子,實際節(jié)點個數(shù) * numberOfReplicas =虛擬節(jié)點個數(shù)
   private final SortedMap<Long, T> circle = new TreeMap<Long, T>(); 
  // 存儲虛擬節(jié)點的hash值到真實節(jié)點的映射
   public ConsistentHash(HashFunction hashFunction, int numberOfReplicas,
           Collection<T> nodes) {
       this.hashFunction = hashFunction;
       this.numberOfReplicas = numberOfReplicas; 
       for (T node : nodes)
           add(node); 
  }
   public void add(T node) {
       for (int i = 0; i < numberOfReplicas; i++)
           // 對于一個實際機器節(jié)點 node, 對應(yīng) numberOfReplicas 個虛擬節(jié)點
           /*            * 不同的虛擬節(jié)點(i不同)有不同的hash值,但都對應(yīng)同一個實際機器node
            * 虛擬node一般是均衡分布在環(huán)上的,數(shù)據(jù)存儲在順時針方向的虛擬node上
            */
           circle.put(hashFunction.hash(node.toString() + i), node);
   }
   public void remove(T node) {
       for (int i = 0; i < numberOfReplicas; i++)
           circle.remove(hashFunction.hash(node.toString() + i));
   }
   /*
    * 獲得一個最近的順時針節(jié)點,根據(jù)給定的key 取Hash
    * 然后再取得順時針方向上最近的一個虛擬節(jié)點對應(yīng)的實際節(jié)點
    * 再從實際節(jié)點中取得 數(shù)據(jù)
    */
   public T get(Object key) {
       if (circle.isEmpty())
           return null;
       long hash = hashFunction.hash((String) key);
       // node 用String來表示,獲得node在哈希環(huán)中的hashCode
       if (!circle.containsKey(hash)) {
       //數(shù)據(jù)映射在兩臺虛擬機器所在環(huán)之間,就需要按順時針方向?qū)ふ覚C器
           SortedMap<Long, T> tailMap = circle.tailMap(hash); 
          hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
       }       return circle.get(hash);
   }   public long getSize() { 
      return circle.size();
   } 
    /*
    * 查看MD5算法生成的hashCode值---表示整個哈希環(huán)中各個虛擬節(jié)點位置 
   */
   public void testBalance(){
       Set<Long> sets  = circle.keySet();
       //獲得TreeMap中所有的Key
       SortedSet<Long> sortedSets= new TreeSet<Long>(sets); 
      //將獲得的Key集合排序
       for(Long hashCode : sortedSets){ 
          System.out.println(hashCode);
       } 
             System.out.println("----each location 's distance are follows: ----");
       /*
        * 查看用MD5算法生成的long hashCode 相鄰兩個hashCode的差值
        */
      Iterator<Long> it = sortedSets.iterator(); 
      Iterator<Long> it2 = sortedSets.iterator();
      if(it2.hasNext()) 
          it2.next(); 
      long keyPre, keyAfter;
      while(it.hasNext() && it2.hasNext()){
           keyPre = it.next();
           keyAfter = it2.next();
           System.out.println(keyAfter - keyPre);
       }
   }      
public static void main(String[] args) {
       Set<String> nodes = new HashSet<String>();
       nodes.add("A");
       nodes.add("B");
       nodes.add("C"); 
             ConsistentHash<String> consistentHash = new ConsistentHash<String>(new HashFunction(), 2, nodes);       
consistentHash.add("D"); 
             System.out.println("hash circle size: " + consistentHash.getSize()); 
      System.out.println("location of each node are follows: "); 
      consistentHash.testBalance();
   }
   }

參考:
https://mp.weixin.qq.com/s/k-2Lb8OKWHOcyNyX23zU1Q

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末级乍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子帚湘,更是在濱河造成了極大的恐慌玫荣,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件客们,死亡現(xiàn)場離奇詭異,居然都是意外死亡材诽,警方通過查閱死者的電腦和手機底挫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脸侥,“玉大人建邓,你說我怎么就攤上這事≌稣恚” “怎么了官边?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長外遇。 經(jīng)常有香客問我注簿,道長,這世上最難降的妖魔是什么跳仿? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任诡渴,我火速辦了婚禮,結(jié)果婚禮上菲语,老公的妹妹穿的比我還像新娘妄辩。我一直安慰自己惑灵,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布眼耀。 她就那樣靜靜地躺著英支,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哮伟。 梳的紋絲不亂的頭發(fā)上干花,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天,我揣著相機與錄音澈吨,去河邊找鬼把敢。 笑死,一個胖子當(dāng)著我的面吹牛谅辣,可吹牛的內(nèi)容都是我干的修赞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼桑阶,長吁一口氣:“原來是場噩夢啊……” “哼柏副!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚣录,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤割择,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后萎河,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荔泳,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年虐杯,在試婚紗的時候發(fā)現(xiàn)自己被綠了玛歌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡擎椰,死狀恐怖支子,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情达舒,我是刑警寧澤值朋,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站巩搏,受9級特大地震影響昨登,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贯底,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一篙骡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦糯俗、人聲如沸尿褪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杖玲。三九已至,卻和暖如春淘正,著一層夾襖步出監(jiān)牢的瞬間摆马,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工鸿吆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留囤采,地道東北人。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓惩淳,卻偏偏與公主長得像蕉毯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子思犁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,107評論 2 356