本文系轉載,轉載請註明出處:http://www.slimeden.com/2011/09/web/memcached_client_hash
感謝這位兄弟的總結~
當前很多大型的web系統為了減輕資料庫伺服器負載,會採用memchached作為緩衝系統以提高響應速度。
目錄:
- memchached簡介
- 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 – A,B,C。命中B的幾率遠遠高於A和C。
為解決這個問題,需要使用虛擬節點的思想:為每個物理節點(server)在環上分配100~200個點,這樣環上的節點較多,就能抑制分布不均勻。
當為cache定位目標server時,如果定位到虛擬節點上,就表示cache真正的儲存位置是在該虛擬節點代表的實際物理server上。
另外,如果每個實際server的負載能力不同,可以賦予不同的權重,根據權重分配不同數量的虛擬節點。
源碼解析
下面結合一個java的memcached client(gwhalin / Memcached-Java-Client)的源碼來看一下consistent hash的實現。
首先看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-factorbyte[] 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個虛擬節點}
每個server根據權重獲得一個虛擬節點數量控制因子factor,然後由services[i]+”-”+i來產生factor個hash值,產生hash值時採用MD5演算法。
由於MD5長度是16個位元組,正好劃分成4段,每段4位元組,這樣每段就對應一個虛擬節點。linex-liney通過把這一段的4位元組拼裝成連續的32bit,作為低32位拉升為一個Long。
為key定位cache儲存的server:
// 用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的第一個虛擬節點對應的即是目標serverSortedMap tmap = this.consistentBuckets.tailMap( hv );return ( tmap.isEmpty() ) ? this.consistentBuckets.firstKey() : tmap.firstKey();
3. 參考資料
- 首次提出consistent hash的論文 –
“Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web”
- Consistent hashing Wiki –
http://en.wikipedia.org/wiki/Consistent_hashing
- Ketama: Consistent Hashing
- memcached開源項目首頁
- memcached google code首頁
- gwhalin / Memcached-Java-Client首頁