當前位置:編程學習大全網 - 源碼下載 - php的memcached分布式hash算法,如何解決分布不均?crc32這個算法沒辦法把key值均勻的分布出去

php的memcached分布式hash算法,如何解決分布不均?crc32這個算法沒辦法把key值均勻的分布出去

memcached的總結和分布式壹致性hash

當前很多大型的web系統為了減輕數據庫服務器負載,會采用memchached作為緩存系統以提高響應速度。

目錄: (/lesson.html)

memchached簡介

hash

取模

壹致性hash

虛擬節點

源碼解析

參考資料

1. memchached簡介

memcached是壹個開源的高性能分布式內存對象緩存系統。

其實思想還是比較簡單的,實現包括server端(memcached開源項目壹般只單指server端)和client端兩部分:

server端本質是壹個in-memory key-value store,通過在內存中維護壹個大的hashmap用來存儲小塊的任意數據,對外通過統壹的簡單接口(memcached protocol)來提供操作。

client端是壹個library,負責處理memcached protocol的網絡通信細節,與memcached server通信,針對各種語言的不同實現分裝了易用的API實現了與不同語言平臺的集成。

web系統則通過client庫來使用memcached進行對象緩存。

2. hash

memcached的分布式主要體現在client端,對於server端,僅僅是部署多個memcached server組成集群,每個server獨自維護自己的數據(互相之間沒有任何通信),通過daemon監聽端口等待client端的請求。

而在client端,通過壹致的hash算法,將要存儲的數據分布到某個特定的server上進行存儲,後續讀取查詢使用同樣的hash算法即可定位。

client端可以采用各種hash算法來定位server:

取模

最簡單的hash算法

targetServer = serverList[hash(key) % serverList.size]

直接用key的hash值(計算key的hash值的方法可以自由選擇,比如算法CRC32、MD5,甚至本地hash系統,如java的hashcode)模上server總數來定位目標server。這種算法不僅簡單,而且具有不錯的隨機分布特性。

但是問題也很明顯,server總數不能輕易變化。因為如果增加/減少memcached server的數量,對原先存儲的所有key的後續查詢都將定位到別的server上,導致所有的cache都不能被命中而失效。

壹致性hash

為了解決這個問題,需要采用壹致性hash算法(consistent hash)

相對於取模的算法,壹致性hash算法除了計算key的hash值外,還會計算每個server對應的hash值,然後將這些hash值映射到壹個有限的值域上(比如0~2^32)。通過尋找hash值大於hash(key)的最小server作為存儲該key數據的目標server。如果找不到,則直接把具有最小hash值的server作為目標server。

為了方便理解,可以把這個有限值域理解成壹個環,值順時針遞增。

如上圖所示,集群中壹***有5個memcached server,已通過server的hash值分布到環中。

如果現在有壹個寫入cache的請求,首先計算x=hash(key),映射到環中,然後從x順時針查找,把找到的第壹個server作為目標server來存儲cache,如果超過了2^32仍然找不到,則命中第壹個server。比如x的值介於A~B之間,那麽命中的server節點應該是B節點

可以看到,通過這種算法,對於同壹個key,存儲和後續的查詢都會定位到同壹個memcached server上。

那麽它是怎麽解決增/刪server導致的cache不能命中的問題呢?

假設,現在增加壹個server F,如下圖

此時,cache不能命中的問題仍然存在,但是只存在於B~F之間的位置(由C變成了F),其他位置(包括F~C)的cache的命中不受影響(刪除server的情況類似)。盡管仍然有cache不能命中的存在,但是相對於取模的方式已經大幅減少了不能命中的cache數量。

虛擬節點

但是,這種算法相對於取模方式也有壹個缺陷:當server數量很少時,很可能他們在環中的分布不是特別均勻,進而導致cache不能均勻分布到所有的server上。

如圖,壹***有3臺server – 1,2,4。命中4的幾率遠遠高於1和2。

為解決這個問題,需要使用虛擬節點的思想:為每個物理節點(server)在環上分配100~200個點,這樣環上的節點較多,就能抑制分布不均勻。

當為cache定位目標server時,如果定位到虛擬節點上,就表示cache真正的存儲位置是在該虛擬節點代表的實際物理server上。

另外,如果每個實際server的負載能力不同,可以賦予不同的權重,根據權重分配不同數量的虛擬節點。

// 采用有序map來模擬環

this.consistentBuckets = new TreeMap();

MessageDigest md5 = MD5.get();//用MD5來計算key和server的hash值

// 計算總權重

if ( this.totalWeight for ( int i = 0; i < this.weights.length; i++ )

this.totalWeight += ( this.weights[i] == null ) ? 1 : this.weights[i];

} else if ( this.weights == null ) {

this.totalWeight = this.servers.length;

}

// 為每個server分配虛擬節點

for ( int i = 0; i < servers.length; i++ ) {

// 計算當前server的權重

int thisWeight = 1;

if ( this.weights != null && this.weights[i] != null )

thisWeight = this.weights[i];

// factor用來控制每個server分配的虛擬節點數量

// 權重都相同時,factor=40

// 權重不同時,factor=40*server總數*該server權重所占的百分比

// 總的來說,權重越大,factor越大,可以分配越多的虛擬節點

double factor = Math.floor( ((double)(40 * this.servers.length * thisWeight)) / (double)this.totalWeight );

for ( long j = 0; j < factor; j++ ) {

// 每個server有factor個hash值

// 使用server的域名或IP加上編號來計算hash值

// 比如server - "172.45.155.25:11111"就有factor個數據用來生成hash值:

// 172.45.155.25:11111-1, 172.45.155.25:11111-2, ..., 172.45.155.25:11111-factor

byte[] d = md5.digest( ( servers[i] + "-" + j ).getBytes() );

// 每個hash值生成4個虛擬節點

for ( int h = 0 ; h < 4; h++ ) {

Long k =

((long)(d[3+h*4]&0xFF) << 24)

| ((long)(d[2+h*4]&0xFF) << 16)

| ((long)(d[1+h*4]&0xFF) << 8 )

| ((long)(d[0+h*4]&0xFF));

// 在環上保存節點

consistentBuckets.put( k, servers[i] );

}

}

// 每個server壹***分配4*factor個虛擬節點

}

// 采用有序map來模擬環

this.consistentBuckets = new TreeMap();

MessageDigest md5 = MD5.get();//用MD5來計算key和server的hash值

// 計算總權重

if ( this.totalWeight for ( int i = 0; i < this.weights.length; i++ )

this.totalWeight += ( this.weights[i] == null ) ? 1 : this.weights[i];

} else if ( this.weights == null ) {

this.totalWeight = this.servers.length;

}

// 為每個server分配虛擬節點

for ( int i = 0; i < servers.length; i++ ) {

// 計算當前server的權重

int thisWeight = 1;

if ( this.weights != null && this.weights[i] != null )

thisWeight = this.weights[i];

// factor用來控制每個server分配的虛擬節點數量

// 權重都相同時,factor=40

// 權重不同時,factor=40*server總數*該server權重所占的百分比

// 總的來說,權重越大,factor越大,可以分配越多的虛擬節點

double factor = Math.floor( ((double)(40 * this.servers.length * thisWeight)) / (double)this.totalWeight );

for ( long j = 0; j < factor; j++ ) {

// 每個server有factor個hash值

// 使用server的域名或IP加上編號來計算hash值

// 比如server - "172.45.155.25:11111"就有factor個數據用來生成hash值:

// 172.45.155.25:11111-1, 172.45.155.25:11111-2, ..., 172.45.155.25:11111-factor

byte[] d = md5.digest( ( servers[i] + "-" + j ).getBytes() );

// 每個hash值生成4個虛擬節點

for ( int h = 0 ; h < 4; h++ ) {

Long k =

((long)(d[3+h*4]&0xFF) << 24)

| ((long)(d[2+h*4]&0xFF) << 16)

| ((long)(d[1+h*4]&0xFF) << 8 )

| ((long)(d[0+h*4]&0xFF));

// 在環上保存節點

consistentBuckets.put( k, servers[i] );

}

}

// 每個server壹***分配4*factor個虛擬節點

}

// 用MD5來計算key的hash值

MessageDigest md5 = MD5.get();

md5.reset();

md5.update( key.getBytes() );

byte[] bKey = md5.digest();

// 取MD5值的低32位作為key的hash值

long hv = ((long)(bKey[3]&0xFF) << 24) | ((long)(bKey[2]&0xFF) << 16) | ((long)(bKey[1]&0xFF) << 8 ) | (long)(bKey[0]&0xFF);

// hv的tailMap的第壹個虛擬節點對應的即是目標server

SortedMap tmap = this.consistentBuckets.tailMap( hv );

return ( tmap.isEmpty() ) ? this.consistentBuckets.firstKey() : tmap.firstKey();

更多問題到問題求助專區(/)

  • 上一篇:uniapp實現掃碼OCR兩功能的小程序開發到上線
  • 下一篇:網絡遊戲是哪壹年進入中國的
  • copyright 2024編程學習大全網