memcached分布式緩存應用教程
1.什么是memcached
許多Web應用都將數據保存到RDBMS中,應用服務器從中讀取數據并在瀏覽器中顯示。但隨著數據量的增大、訪問的集中,就會出現 RDBMS的負擔加重、數據庫響應惡化、網站顯示延遲等重大影響。這時就該memcached大顯身手了。memcached是高性能的分布式內存緩存服 務器。一般的使用目的是,通過緩存數據庫查詢結果,減少數據庫訪問次數,以提高動態Web應用的速度、提高可擴展性。

2.memcached特性
?協議簡單
?基于libevent的事件處理
?內置內存存儲方式
?memcached不互相通信的分布式
3.理解memcached的內存存儲
最近的memcached默認情況下采用了名為Slab Allocator的機制分配、管理內存。在該機制出現以前,內存的分配是通過對所有記錄簡單地進行malloc和free來進行的。但是,這種方式會導 致內存碎片,加重操作系統內存管理器的負擔,最壞的情況下,會導致操作系統比memcached進程本身還慢。Slab Allocator就是為解決該問題而誕生的。
也就是說,Slab Allocator的基本原理是按照預先規定的大小,將分配的內存分割成特定長度的塊,以完全解決內存碎片問題。
下圖即為Slab Allocation的構造圖:

緩存記錄的原理
下面說明memcached如何針對客戶端發送的數據選擇slab并緩存到chunk中。
memcached根據收到的數據的大小,選擇最適合數據大小的slab。memcached中保存著slab內空閑chunk的列表,根據該列表選擇chunk,然后將數據緩存于其中。
實際上,Slab Allocator也是有利也有弊。下面介紹一下它的缺點。
Slab Allocator解決了當初的內存碎片問題,但新的機制也給memcached帶來了新的問題。
這個問題就是,由于分配的是特定長度的內存,因此無法有效利用分配的內存。例如,將100字節的數據緩存到128字節的chunk中,剩余的28字節就浪費了
4.使用Growth Factor進行調優
memcached在啟動時指定Growth Factor因子(通過-f選項),就可以在某種程度上控制slab之間的差異。默認值為1.25。但是,在該選項出現之前,這個因子曾經固定為2,稱為“powers of 2”策略。
讓我們用以前的設置,以verbose模式啟動memcached試試看:
$ memcached -f 2 -vv
5.查看memcached的內部狀態
memcached有個名為stats的命令,使用它可以獲得各種各樣的信息。執行命令的方法很多,用telnet最為簡單:
$ telnet 主機名 端口號
連接到memcached之后,輸入stats再按回車,即可獲得包括資源利用率在內的各種信息。此外,輸入"stats slabs"或"stats items"還可以獲得關于緩存記錄的信息。結束程序請輸入quit。
6.memcached的分布式算法
memcached雖然稱為“分布式”緩存服務器,但服務器端并沒有“分布式”功能。服務器端僅包括第2次、第3次前坂介紹的內存存儲功能,其實現非常簡單。至于memcached的分布式,則是完全由客戶端程序庫實現的。這種分布式是memcached的最大特點。
這里多次使用了“分布式”這個詞,但并未做詳細解釋。現在開始簡單地介紹一下其原理,各個客戶端的實現基本相同。
6.1分布式客戶端算法
6.1.1根據余數計算分散
有點:簡單易實現
缺點:余數計算的方法簡單,數據的分散性也相當優秀,但也有其缺點。那就是當添加或移除服務器時,緩存重組的代價相當巨大。添加服務器后,余數就會產生巨變,這樣就無法獲取與保存時相同的服務器,從而影響緩存的命中率。
6.1.2 一致性hash算法Consistent Hashing
Consistent Hashing的簡單說明
Consistent Hashing如下所示:首先求出memcached服務器(節點)的哈希值,并將其配置到0~232的圓(continuum)上。然后用同樣的方法求 出存儲數據的鍵的哈希值,并映射到圓上。然后從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務器上。如果超過232仍然找不到服務器,就 會保存到第一臺memcached服務器上。
Consistent Hashing基本原理如下圖:

從上圖的狀態中添加一臺memcached服務器。余數分布式算法由于保存鍵的服務器會發生巨大變化而影響緩存的命中率,但Consistent Hashing中,只有在continuum上增加服務器的地點逆時針方向的第一臺服務器上的鍵會受到影響。增加服務器節點如下圖所示:

因此,Consistent Hashing最大限度地抑制了鍵的重新分布。而且,有的Consistent Hashing的實現方法還采用了虛擬節點的思想。使用一般的hash函數的話,服務器的映射地點的分布非常不均勻。因此,使用虛擬節點的思想,為每個物 理節點(服務器)在continuum上分配100~200個點。這樣就能抑制分布不均勻,最大限度地減小服務器增減時的緩存重新分布。
通過下文中介紹的使用Consistent Hashing算法的memcached客戶端函數庫進行測試的結果是,由服務器臺數(n)和增加的服務器臺數(m)計算增加服務器后的命中率計算公式如下:
(1 - n/(n+m)) * 100
1.6.3Consistent Hashing java客戶端實現:
下面是參照的簡單實現:
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.SortedMap; import java.util.TreeMap;public class ConsistentHash { private final HashFunction hashFunction; private final int numberOfReplicas; static SortedMap<Long, String> circle = new TreeMap<Long, String>();
public static void main(String args[]){ List<String> list = new ArrayList<String>(); String node1 = "node1"; String node2 = "node2"; String node3 = "node3"; list.add(node1); list.add(node2); list.add(node3); HashFunction hf = new HashFunction(); ConsistentHash ch = new ConsistentHash(hf,1,list); System.out.println("位置1:"+ch.get("abcdefg")); System.out.println("位置2:"+ch.get("loadfsf")); System.out.println("位置3:"+ch.get("ilfcdef")); System.out.println("位置4:"+ch.get("mbcdefg")); System.out.println("位置5:"+ch.get("tbcdefg")); ch.add("node4"); ch.add("node5"); ch.add("node6"); System.out.println("==========================="); System.out.println("位置1:"+ch.get("abcdefg")); System.out.println("位置2:"+ch.get("loadfsf")); System.out.println("位置3:"+ch.get("ilfcdef")); System.out.println("位置4:"+ch.get("mbcdefg")); System.out.println("位置5:"+ch.get("tbcdefg")); } public ConsistentHash(HashFunction hashFunction, int numberOfReplicas, Collection<String> nodes) { this.hashFunction = hashFunction; this.numberOfReplicas = numberOfReplicas; for (String node : nodes) { add(node); } } public void add(String node) { for (int i = 0; i < numberOfReplicas; i++) { circle.put(hashFunction.hash(node.toString() +":" + i), node); } } public void remove(String node) { for (int i = 0; i < numberOfReplicas; i++) { circle.remove(hashFunction.hash(node.toString() + ":" + i)); } } public String get(String key) { if (circle.isEmpty()) { return null; } long hash = hashFunction.hash(key); SortedMap<Long, String> tailMap = circle.tailMap(hash); hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); return circle.get(hash); }} class HashFunction{ MessageDigest md5=null; public Long hash(String key) {
if(md5==null) { try { md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException( "++++ no md5 algorythm found"); } } md5.reset(); md5.update(key.getBytes()); byte[] bKey = md5.digest(); long res = ((long)(bKey[3]&0xFF) << 24) | ((long)(bKey[2]&0xFF) << 16) | ((long)(bKey[1]&0xFF) << 8) | (long)(bKey[0]&0xFF); return res; }}</pre>
來自:http://blog.csdn.net/toweryangtao/article/details/8617032