深入學習Memcached,memcached學習
由於實驗室在一個項目中用到了Memcached分布式緩衝,自己這段時間也對分布式緩衝深入學習了一下,本文就總結一下自己的收穫,還是從Memcached是什麼談起吧。
Memcached是什麼
Memcached是一款高效能的分布式記憶體對象緩衝系統,使用它可以減少應用系統對資料庫的直接存取,減輕了資料庫負載,並且提升了應用程式的響應速度。可以將Memcached比作一個巨大的、儲存了很多
LRU演算法
作業系統的頁面置換演算法中也包括該演算法,這種演算法非常適合於緩衝系統。它基於一種假設:過去一段時間使用頻率最低的資料,將來也會很少使用。下面是我自己實現的一個基於LRU演算法的緩衝,它實現了基本的緩衝功能,不過需要改進的一點是該緩衝應該是單例的。
package colin.cache;/** * * @author Colin Wang * Created on Apr 18, 2015 */public class LRUCache { private static final int DEFAULT_MAX_SIZE = 10; private Object[] elements; private int size; public LRUCache(int maxSize) { elements = new Object[maxSize]; } public LRUCache() { this(DEFAULT_MAX_SIZE); } public boolean isEmpty() { return size == 0; } public int size() { return size; } public boolean isFull() { return size == elements.length; } /** * 返回元素在數組中的位置 * * @param element * @return */ public int indexOf(Object element) { for (int i = 0; i < size; i++) { if (element.equals(elements[i])) { return i; } } return -1; } public Object push(Object element) { int index = -1; if (!isFull() && indexOf(element) == -1) { // 緩衝未滿,並且其中不含有待插入的元素 elements[size++] = element; } else if (isFull() && indexOf(element) == -1) { // 緩衝已滿,並且其中不含有待插入的元素 for (int i = 0; i < size - 1; i++) { elements[i] = elements[i + 1]; } elements[size - 1] = element; } else { index = indexOf(element); for (int i = index; i < size - 1; i++) { elements[i] = elements[i + 1]; } elements[size - 1] = element; } return index == -1 ? null : elements[index]; } public void show() { for (int i = 0; i < size; i++) { System.out.print(elements[i].toString() + " "); } }}
一致性雜湊演算法
該演算法在分布式緩衝系統中應用十分廣泛,它的關鍵之處在於使用環形的雜湊空間。首先我們先考慮一下JDK中HashMap的實現,我們知道,HashMap有一個初始容量和裝載因子,當HashMap中元素的數量達到了初始容量*裝載因子
這麼多時,HashMap就會進行擴容(變為原來的2倍)然後再雜湊,當元素很多時,這種再雜湊操作實際上是很消耗資源的。儘管HashMap使用了一種比較最佳化的方式,比如HashMap的大小總是為2的n次冪(預設大小為16),這樣既減少了雜湊碰撞的幾率(因為2的n次冪-1
轉化為二進位時所有位均為1,這樣就避免了某些位不能發揮作用),又增加了HashMap在定位元素時的效率,比如方法:
static int indexFor(int h, int length) { return h & (length-1);}
當length總是2的n次方時,h & (length - 1)
運算等價於對length模數。但是這些仍然不能完全彌補所有元素必須全部重新雜湊的弱點。比如我們擁有N台快取服務器,所有的熱資料已經通過Hash演算法映射到了這N台伺服器上,這時候當其中一台快取服務器掛掉,或者我們又新添了幾台快取服務器進來時,所有已經映射好的資料都將失效,而我們的後端資料庫將會承受這一切!所以,一致性Hash演算法就派上用場了,上面已經提到,一致性雜湊演算法使用的是環形的雜湊空間,如所示:
常規的Hash演算法為將某個key映射為一個32位整數,我們將這0至2^32-1的數值空間首位相接便形成了一個環。然後把我們的快取服務器通過散列(對IP或機器名等進行散列)映射到這個環上,這樣,當一個對象被散列到某個位置後,我們就把該對象緩衝到順時針方向離它最近的快取服務器結點上。使用這種方式,當其中一個快取服務器結點失效時,不會導致所有的快取資料都失效,也僅僅需要把原來被緩衝到該結點上的資料繼續緩衝到下一個順時針方向離它最近的結點上即可。當新增一個結點時,同樣也只需要對很少的一部分資料進行再雜湊並映射到新的結點上即可。為了避免出現資料分配不均勻的情況,比如絕大多數的資料都被緩衝到了一個結點上,而其他的結點則緩衝很少的資料,一致性雜湊演算法還提出了虛擬結點的概念。如所示:
A1和A2為結點A的虛擬結點,虛擬結點的hash值計算可以採用對應結點的IP地址加數字尾碼的方式,如“192.168.1.1#1”。這樣就可以將被散列到A1和A2的資料都緩衝進結點A中,從而避免了資料分布不平衡的現象。