一致性哈希算法

tencent2012 筆試題附加題

     問題描述:  例如手機朋友網有n個服務器,爲了方便用戶的訪問會在服務器上緩存數據,因此用戶每次訪問的時候最好能保持同一臺服務器。已有的做法是根據ServerIPIndex[QQNUM%n]得到請求的服務器,這種方法很方便將用戶分到不同的服務器上去。但是如果一臺服務器死掉了,那麼n就變爲了n-1,那麼ServerIPIndex[QQNUM%n]與ServerIPIndex[QQNUM%(n-1)]基本上都不一樣了,所以大多數用戶的請求都會轉到其他服務器,這樣會發生大量訪問錯誤。

 問:  如何改進或者換一種方法,使得:

(1)一臺服務器死掉後,不會造成大面積的訪問錯誤,

(2)原有的訪問基本還是停留在同一臺服務器上;

(3)儘量考慮負載均衡。

 

解決思路,採用一致性哈希方法可以解決此問題:見下文:

                  轉自http://blog.csdn.net/sparkliang/article/details/5279393

               一致性 hash 算法( consistent hashing )

張亮

consistent hashing 算法早在 1997 年就在論文 Consistent hashing and random trees 中被提出,目前在 cache 系統中應用越來越廣泛;

1 基本場景

比如你有 N 個 cache 服務器(後面簡稱 cache ),那麼如何將一個對象 object 映射到 N 個 cache 上呢,你很可能會採用類似下面的通用方法計算 object 的 hash 值,然後均勻的映射到到 N 個 cache ;

hash(object)%N

一切都運行正常,再考慮如下的兩種情況;

1 一個 cache 服務器 m down 掉了(在實際應用中必須要考慮這種情況),這樣所有映射到 cache m 的對象都會失效,怎麼辦,需要把 cache m 從 cache 中移除,這時候 cache 是 N-1 臺,映射公式變成了 hash(object)%(N-1) ;

2 由於訪問加重,需要添加 cache ,這時候 cache 是 N+1 臺,映射公式變成了 hash(object)%(N+1) ;

1 和 2 意味着什麼?這意味着突然之間幾乎所有的 cache 都失效了。對於服務器而言,這是一場災難,洪水般的訪問都會直接衝向後臺服務器;

再來考慮第三個問題,由於硬件能力越來越強,你可能想讓後面添加的節點多做點活,顯然上面的 hash 算法也做不到。

  有什麼方法可以改變這個狀況呢,這就是 consistent hashing…

2 hash 算法和單調性

   Hash 算法的一個衡量指標是單調性( Monotonicity ),定義如下:

  單調性是指如果已經有一些內容通過哈希分派到了相應的緩衝中,又有新的緩衝加入到系統中。哈希的結果應能夠保證原有已分配的內容可以被映射到新的緩衝中去,而不會被映射到舊的緩衝集合中的其他緩衝區。

容易看到,上面的簡單 hash 算法 hash(object)%N 難以滿足單調性要求。

3 consistent hashing 算法的原理

consistent hashing 是一種 hash 算法,簡單的說,在移除 / 添加一個 cache 時,它能夠儘可能小的改變已存在 key 映射關係,儘可能的滿足單調性的要求。

下面就來按照 5 個步驟簡單講講 consistent hashing 算法的基本原理。

3.1 環形hash 空間

考慮通常的 hash 算法都是將 value 映射到一個 32 爲的 key 值,也即是 0~2^32-1 次方的數值空間;我們可以將這個空間想象成一個首( 0 )尾( 2^32-1 )相接的圓環,如下面圖 1 所示的那樣。

circle space

圖 1 環形 hash 空間

3.2 把對象映射到hash 空間

接下來考慮 4 個對象 object1~object4 ,通過 hash 函數計算出的 hash 值 key 在環上的分佈如圖 2 所示。

hash(object1) = key1;

… …

hash(object4) = key4;

object

圖 2 4 個對象的 key 值分佈

3.3 把cache 映射到hash 空間

Consistent hashing 的基本思想就是將對象和 cache 都映射到同一個 hash 數值空間中,並且使用相同的hash 算法。

假設當前有 A,B 和 C 共 3 臺 cache ,那麼其映射結果將如圖 3 所示,他們在 hash 空間中,以對應的 hash值排列。

hash(cache A) = key A;

… …

hash(cache C) = key C;

cache

圖 3 cache 和對象的 key 值分佈

 

說到這裏,順便提一下 cache 的 hash 計算,一般的方法可以使用 cache 機器的 IP 地址或者機器名作爲hash 輸入。

3.4 把對象映射到cache

現在 cache 和對象都已經通過同一個 hash 算法映射到 hash 數值空間中了,接下來要考慮的就是如何將對象映射到 cache 上面了。

在這個環形空間中,如果沿着順時針方向從對象的 key 值出發,直到遇見一個 cache ,那麼就將該對象存儲在這個 cache 上,因爲對象和 cache 的 hash 值是固定的,因此這個 cache 必然是唯一和確定的。這樣不就找到了對象和 cache 的映射方法了嗎?!

依然繼續上面的例子(參見圖 3 ),那麼根據上面的方法,對象 object1 將被存儲到 cache A 上; object2和 object3 對應到 cache C ; object4 對應到 cache B ;

3.5 考察cache 的變動

前面講過,通過 hash 然後求餘的方法帶來的最大問題就在於不能滿足單調性,當 cache 有所變動時,cache 會失效,進而對後臺服務器造成巨大的衝擊,現在就來分析分析 consistent hashing 算法。

3.5.1 移除 cache

考慮假設 cache B 掛掉了,根據上面講到的映射方法,這時受影響的將僅是那些沿 cache B 逆時針遍歷直到下一個 cache  cache A (原文筆誤寫爲cacheC之間的對象,也即是本來映射到 cache B 上的那些對象。

因此這裏僅需要變動對象 object4 ,將其重新映射到 cache C 上即可;參見圖 4 。

remove

圖 4 Cache B 被移除後的 cache 映射

3.5.2 添加 cache

再考慮添加一臺新的 cache D 的情況,假設在這個環形 hash 空間中, cache D 被映射在對象 object2 和object3 之間。這時受影響的將僅是那些沿 cache D 逆時針遍歷直到下一個 cache ( cache B )之間的對象(它們是也本來映射到 cache C 上對象的一部分),將這些對象重新映射到 cache D 上即可。

 

因此這裏僅需要變動對象 object2 ,將其重新映射到 cache D 上;參見圖 5 。

add

圖 5 添加 cache D 後的映射關係

4 虛擬節點

考量 Hash 算法的另一個指標是平衡性 (Balance) ,定義如下:

平衡性

  平衡性是指哈希的結果能夠儘可能分佈到所有的緩衝中去,這樣可以使得所有的緩衝空間都得到利用。

hash 算法並不是保證絕對的平衡,如果 cache 較少的話,對象並不能被均勻的映射到 cache 上,比如在上面的例子中,僅部署 cache A 和 cache C 的情況下,在 4 個對象中, cache A 僅存儲了 object1 ,而 cache C 則存儲了 object2 、 object3 和 object4 ;分佈是很不均衡的。

爲了解決這種情況, consistent hashing 引入了“虛擬節點”的概念,它可以如下定義:

“虛擬節點”( virtual node )是實際節點在 hash 空間的複製品( replica ),一實際個節點對應了若干個“虛擬節點”,這個對應個數也成爲“複製個數”,“虛擬節點”在 hash 空間中以 hash 值排列。

仍以僅部署 cache A 和 cache C 的情況爲例,在圖 4 中我們已經看到, cache 分佈並不均勻。現在我們引入虛擬節點,並設置“複製個數”爲 2 ,這就意味着一共會存在 4 個“虛擬節點”, cache A1, cache A2 代表了 cache A ; cache C1, cache C2 代表了 cache C ;假設一種比較理想的情況,參見圖 6 。

virtual nodes

圖 6 引入“虛擬節點”後的映射關係

 

此時,對象到“虛擬節點”的映射關係爲:

objec1->cache A2 ; objec2->cache A1 ; objec3->cache C1 ; objec4->cache C2 ;

因此對象 object1 和 object2 都被映射到了 cache A 上,而 object3 和 object4 映射到了 cache C 上;平衡性有了很大提高。

引入“虛擬節點”後,映射關係就從 { 對象 -> 節點 } 轉換到了 { 對象 -> 虛擬節點 } 。查詢物體所在 cache時的映射關係如圖 7 所示。

map

圖 7 查詢對象所在 cache

 

“虛擬節點”的 hash 計算可以採用對應節點的 IP 地址加數字後綴的方式。例如假設 cache A 的 IP 地址爲202.168.14.241 。

引入“虛擬節點”前,計算 cache A 的 hash 值:

Hash(“202.168.14.241”);

引入“虛擬節點”後,計算“虛擬節”點 cache A1 和 cache A2 的 hash 值:

Hash(“202.168.14.241#1”);  // cache A1

Hash(“202.168.14.241#2”);  // cache A2

 

問題:1.請問虛擬節點,怎麼保證均勻分佈在,那個環上呢?

這就要由hash算法來保證了,均勻分佈是概率上的均勻,當虛擬節點足夠時,就能保證大概均勻了。

         2.假如cache通過hash函數計算出的值 object通過hash函數計算出來的值是同一個hash值怎麼辦?

object應該指向哪個cache?

 


C++實現方法:轉自:http://www.cnblogs.com/coser/archive/2011/11/27/2265134.html


    一致性hash算法實現有兩個關鍵問題需要解決,一個是用於結點存儲和查找的數據結構的選擇,另一個是結點hash算法的選擇。

    首先來談一下一致性hash算法中用於存儲結點的數據結構。通過了解一致性hash的原理,我們知道結點可以想象爲是存儲在一個環形的數據結構上(如下圖),結點A、B、C、D按hash值在環形分佈上是有序的,也就是說結點可以按hash值存儲在一個有序的隊列裏。如下圖所示,當一個hash值爲-2^20的請求點P查找路由結點時,一致性hash算法會按hash值的順時針方向路由到第一個結點上(B),也就是相當於要在存儲結點的有序結構中,按查詢的key值找到大於key值中的最小的那個結點。因此,我們應該選擇一種數據結構,它應該高效地支持結點頻繁地增刪,也必須具有理想的查詢效率。那麼,紅黑樹可以滿足這些要求。紅黑樹是一顆近似平衡的一顆二叉查找樹,因爲操作比如插入、刪除和查找某個值的最壞情況時間都要求與樹的高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的,而不同於普通的二叉查找樹。 因此,我們選擇使用紅黑樹作爲結點的存儲結構,除了需要實現紅黑樹基本的插入、刪除、查找的基本功能,我們還應該增加另一個查詢lookup函數,用於查找大於key中最小的結點

image

   接下來,我們來說hash算法的選擇。一致性hash算法最初提出來,就是爲了解決負載均衡的問題。每個實體結點會包含很多虛擬結點,虛擬結點是平衡負載的關鍵。我們希望虛擬結點可以均衡的散列在整個“環”上,這樣不僅可以負載到不同hash值的路由請求,還可以當某個結點down掉,原來路由到down掉結點的請求也可以較均衡的路由到其他結點而不會對某個結點造成大量的負載請求。這裏,我們選擇使用MD5算法。通過MD5算法,可以將一個標示串(用於標示虛擬結點)轉化得到一個16字節的字符數組,再對該數組進行處理,得到一個整形的hash值。由於MD5具有高度的離散性,所以生成的hash值也會具有很大的離散性,會均衡的散列到“環”上。

   筆者用C++語言對一致性hash算法進行了實現,下面我將會描述下一些關鍵細節。

1、首先定義實體結點類、虛擬結點類。一個實體結點對應多個虛擬結點。

  實體結點 CNode_s:

?



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*實體結點*/
class CNode_s
{
public:
    /*構造函數*/
    CNode_s();
    CNode_s(char * pIden , int pVNodeCount , void * pData);
 
    /*獲取結點標示*/
    const char * getIden();
 
    /*獲取實體結點的虛擬結點數量*/
    int getVNodeCount();
 
    /*設置實體結點數據值*/
    void setData(void * data);
 
    /*獲取實體結點數據值*/
    void * getData();
private:
    void setCNode_s(char * pIden, int pVNodeCount , void * pData);
    char iden[100];/*結點標示串*/
    int vNodeCount;/*虛擬結點數目*/
    void * data;/*數據結點*/
};



   虛擬結點 CVirtualNode_s:虛擬結點有一指針指向實體結點

?



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*虛擬結點*/
class CVirtualNode_s
{
public:
    /*構造函數*/
    CVirtualNode_s();
    CVirtualNode_s(CNode_s * pNode);
 
    /*設置虛擬結點所指向的實體結點*/
    void setNode_s(CNode_s * pNode);
 
    /*獲取虛擬結點所指向的實體結點*/
    CNode_s * getNode_s();
 
    /*設置虛擬結點hash值*/
    void setHash(long pHash);
 
    /*獲取虛擬結點hash值*/
    long getHash();
private:
    long hash;/*hash值*/
    CNode_s * node; /*虛擬結點所指向的實體結點*/
};



 

2、hash算法具有可選擇性,定義一個hash算法接口,方便以後進行其他算法的擴展。

      這裏創建MD5hash類,並繼承該接口,通過MD5算法求hash值。

類圖:

image  

CHashFun接口:

?



1
2
3
4
5
6
7
/*定義Hash函數類接口,用於計算結點的hash值*/
 
class CHashFun
{
public:
    virtual long getHashVal(const char *) = 0;
};



CMD5HashFun 類繼承CHashFun接口,實現獲取hash值的getHashVal函數:

?



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*用MD5算法計算結點的hash值,繼承CHashFun父類*/
class CMD5HashFun : public CHashFun
{
public:
    virtual long getHashVal (const char * );
};
 
long CMD5HashFun::getHashVal(const char * instr)
{
    int i;
    long hash = 0;
    unsignedchar digest[16];
 
    /*調用MD5相關函數,生成instr的MD5碼,存入digest*/
    md5_state_t md5state;
    md5_init(&md5state);
    md5_append(&md5state, (const unsignedchar *)instr,strlen(instr));
    md5_finish(&md5state, digest);
 
    /* 每四個字節構成一個32位整數,
    將四個32位整數相加得到instr的hash值(可能溢出) */
    for(i = 0; i < 4; i++)
    {
        hash += ((long)(digest[i*4 + 3]&0xFF) << 24)
            | ((long)(digest[i*4 + 2]&0xFF) << 16)
            | ((long)(digest[i*4 + 1]&0xFF) <<  8)
            | ((long)(digest[i*4 + 0]&0xFF));
    }
    return hash;
}



 

3、擴展紅黑樹結構中的查找函數,用於查找紅黑樹中大於key值中最小的結點。

?



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
util_rbtree_node_t* util_rbtree_lookup(util_rbtree_t *rbtree, long key)
{
    if((rbtree != NULL) && !util_rbtree_isempty(rbtree))
    {
        util_rbtree_node_t *node = NULL;
        util_rbtree_node_t *temp = rbtree->root;
        util_rbtree_node_t *null = _NULL(rbtree);
        while(temp != null)
        {
            if(key <= temp->key)
            {
                node = temp; /* update node */
                temp = temp->left;
            }
            else if(key > temp->key)
            {
                temp = temp->right;
            }
        }
        /* if node==NULL return the minimum node */
        return ((node != NULL) ? node : util_rbtree_min(rbtree));
    }
    return NULL;
}



4、創建一致性hash類。使其具有插入、刪除、查找實體結點的功能。

具體算法和操作過程已經在代碼註釋中說明。

?



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
class CConHash
{
public:
    /*構造函數*/
    CConHash(CHashFun * pFunc);
     
    /*設置hash函數*/
    void setFunc(CHashFun * pFunc);
     
    /*增加實體結點 , 0代表成功 , -1代表失敗*/
    int addNode_s(CNode_s * pNode);
     
    /*刪除實體結點 , 0代表成功 , -1代表失敗*/
    int delNode_s(CNode_s * pNode);
     
    /*查找實體結點*/
    CNode_s * lookupNode_s(const char * object);
     
    /*獲取一致性hash結構的所有虛擬結點數量*/
    int getVNodes();
private:
    /*Hash函數*/
    CHashFun * func;
    /*虛擬結點總個數*/
    int vNodes;
    /*存儲虛擬結點的紅黑樹*/
    util_rbtree_t * vnode_tree;
};
/*輔助函數,虛擬結點轉化爲紅黑樹結點*/
util_rbtree_node_t * vNode2RBNode(CVirtualNode_s * vnode);
 
 
CConHash::CConHash(CHashFun * pFunc)
{
    /*設置hash函數*/
    assert(pFunc!=NULL);
    this->func = pFunc;
    this->vNodes = 0;
    /*初始化紅黑樹*/
    vnode_tree = new util_rbtree_s();
    util_rbtree_init(vnode_tree);
}
 
int CConHash::addNode_s(CNode_s * pNode)
{
    if(pNode==NULL)return -1;
    int vCount = pNode->getVNodeCount();
    if(vCount<=0)return -1;
    CVirtualNode_s * virtualNode ;
    util_rbtree_node_t * rbNode;
    char str [100];
    char num[10];
    strcpy(str,pNode->getIden());
    long hash = 0;
    /*生成虛擬結點並插入到紅黑樹中*/
    for(int i=0;i<vCount;i++)
    {
        virtualNode = new CVirtualNode_s(pNode);
        /*採用str+“i”的方法產生不同的iden串,用於後面的hash值計算*/
        itoa(i,num,10);
        strcat(str,num);
        hash = func->getHashVal(str);
        virtualNode->setHash(hash);
        if(!util_rbtree_search(vnode_tree,hash))
        {
            /*生成紅黑樹結點*/
            rbNode = vNode2RBNode(virtualNode);
            if(rbNode!=NULL)
            {
                /*將該結點插入到紅黑樹中*/
                util_rbtree_insert(vnode_tree,rbNode);
                this->vNodes++;
            }
        }
    }
    return 0;
}
 
int CConHash::delNode_s(CNode_s * pNode)
{
    if(pNode==NULL)return -1;
    util_rbtree_node_t * rbNode;
    char str [100];
    char num [10];
    strcpy(str,pNode->getIden());
    int vCount = pNode->getVNodeCount();
    long hash = 0;
    CVirtualNode_s * node = NULL;
    /*將該實體結點產生的所有虛擬結點進行刪除*/
    for(int i=0;i<vCount;i++)
    {
        itoa(i,num,10);
        strcat(str,num);/*採用該方法產生不同的iden串*/
        hash = func->getHashVal(str);
        rbNode = util_rbtree_search(vnode_tree,hash);
        if(rbNode!=NULL)
        {
            node = (CVirtualNode_s *) rbNode->data;
            if(node->getNode_s()==pNode && node->getHash()==hash)
            {
                this->vNodes--;
                /*將該結點從紅黑樹中刪除*/
                util_rbtree_delete(vnode_tree,rbNode);
                delete rbNode;
                delete node;
            }
        }
    }
    return 0;
}
 
CNode_s * CConHash::lookupNode_s(const char * object)
{
    if(object==NULL||this->vNodes==0)return NULL;
    util_rbtree_node_t * rbNode;
    int key = this->func->getHashVal(object);
    /*在紅黑樹中查找key值比key大的最小的結點*/
    rbNode = util_rbtree_lookup(vnode_tree,key);
    if(rbNode!=NULL)
    {
        return ((CVirtualNode_s *) rbNode->data)->getNode_s();
    }
    return NULL;
}
 
int CConHash::getVNodes()
{
    return this->vNodes;
}
 
 
util_rbtree_node_t * vNode2RBNode(CVirtualNode_s * vnode)
{
    if(vnode==NULL)return NULL;
    util_rbtree_node_t *rbNode = new util_rbtree_node_t();
    rbNode->key = vnode->getHash();
    rbNode->data = vnode;
    return rbNode;
}  



 

5、創建一個客戶端類,對一致性hash算法進行測試

      寫了一個getIP的函數,模擬隨機產生的IP字符串。

?



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include<iostream>
#include"CNode_s.h"
#include"CVirtualNode_s.h"
#include"CHashFun.h"
#include"CMD5HashFun.h"
#include"CConHash.h"
#include<string.h>
#include<time.h>
 
using namespace std;
 
void getIP(char * IP)
{
    int a=0, b=0 , c=0 , d=0;
    a = rand()%256;
    b = rand()%256;
    c = rand()%256;
    d = rand()%256;
    char aa[4],bb[4],cc[4],dd[4];
    itoa(a, aa, 10);
    itoa(b, bb, 10);
    itoa(c, cc, 10);
    itoa(d, dd, 10);
    strcpy(IP,aa);
    strcat(IP,".");
    strcat(IP,bb);
    strcat(IP,".");
    strcat(IP,cc);
    strcat(IP,".");
    strcat(IP,dd);
}
 
int main()
{
    srand(time(0));
    freopen("out.txt","r",stdin);
    /*定義hash函數*/
    CHashFun * func = new CMD5HashFun();
    /*創建一致性hash對象*/
    CConHash * conhash = new CConHash(func);
 
    /*定義CNode*/
    CNode_s * node1 = new CNode_s("machineA",50,"10.3.0.201");
    CNode_s * node2 = new CNode_s("machineB",80,"10.3.0.202");
    CNode_s * node3 = new CNode_s("machineC",20,"10.3.0.203");
    CNode_s * node4 = new CNode_s("machineD",100,"10.3.0.204");
 
    conhash->addNode_s(node1);
    conhash->addNode_s(node2);
    conhash->addNode_s(node3);
    conhash->addNode_s(node4);
 
    /*動態更改結點數據值*/
//  node1->setData("99999999");
     
    int ans1 ,ans2 ,ans3 ,ans4;
    ans1=ans2=ans3=ans4=0;
     
    char object[100];
    CNode_s * node ;
    /*動態刪除結點*/
    //conhash->delNode_s(node2);
    for(int i =0;i<30;i++)
    {
    //  getIP(object);
    //  cout<<object<<endl;
        cin>>object;
        node = conhash->lookupNode_s(object);
        if(node!=NULL)
        {
            cout<<object<<"----->\t"<<node->getIden()<<" \t "<<(char *)node->getData()<<endl;
            if(strcmp(node->getIden(),"machineA")==0) ans1++;
            if(strcmp(node->getIden(),"machineB")==0) ans2++;
            if(strcmp(node->getIden(),"machineC")==0) ans3++;
            if(strcmp(node->getIden(),"machineD")==0) ans4++;
        }
    }
 
    cout<<"Total test cases : "<<ans1+ans2+ans3+ans4<<endl;
    cout<<"Map to MachineA : "<<ans1<<endl;
    cout<<"Map to MachineB : "<<ans2<<endl;
    cout<<"Map to MachineC : "<<ans3<<endl;
    cout<<"Map to MachineD : "<<ans4<<endl;
    fclose(stdin);
    return 0;
}



6、刪除結點對hash路由的影響測試

image

測試結果截圖:

imageimage

分析:上面兩幅圖,左邊爲原始四個實體結點的路由情況,後面爲刪除結點2(Node2)之後的路由情況。不難發現,MachineB down之後,原先的路由請求,較均衡地負載到了其他機器結點,而且對原先路由到其他結點的請求沒有影響。比如139.149.184.125這個請求仍會路由到MachineD,並不會因爲結點的減少而造成影響。但是,如果是增加實體結點,可能會造成增加前後路由情況不一致的現象,因爲路由區間的更加狹小,但是不會有特別大的影響。 另一方面,可以發現實體結點的虛擬結點個數比例分配情況很大程度影響了結點的負載路由情況,比例大致與虛擬結點個數相一致。

 

總結:

   本文首先通過介紹實現一致性hash算法的關鍵算法和數據結構的選擇分析,選擇了紅黑樹作爲虛擬結點的存儲結構,以及MD5算法作爲Hash函數用於計算結點的hash值。並使用C++語言,對一致性hash算法進行了實現,實現了一致性hash實體結點的增加、刪除、查找等基本功能,並進行了測試分析。由於筆者水平有限,存在很多有待改進的地方,因此本文僅供大家參考、討論學習。

項目源代碼下載:http://files.cnblogs.com/coser/ConsistentHashAlgorithm.rar

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章