Memcache工作原理及安裝使用

Memcache工作原

1       Memcache是什麼

Memcachedanga.com的一個項目,最早是爲 LiveJournal 服務的,目前全世界不少人使用這個緩存項目來構建自己大負載的網站,來分擔數據庫的壓力。

它可以應對任意多個連接,使用非阻塞的網絡IO。由於它的工作機制是在內存中開闢一塊空間,然後建立一個HashTableMemcached自管理這些HashTable

爲什麼會有Memcachememcached兩種名稱?

其實Memcache是這個項目的名稱,而memcached是它服務器端的主程序文件名,

Memcache官方網站:http://www.danga.com/memcached

2     面臨的問題

對於高併發高訪問的Web應用程序來說,數據庫存取瓶頸一直是個令人頭疼的問題。特別當你的程序架構還是建立在單數據庫模式,而一個數據池連接數峯 值已經達到500的時候,那你的程序運行離崩潰的邊緣也不遠了。很多小網站的開發人員一開始都將注意力放在了產品需求設計上,缺忽視了程序整體性能,可擴 展性等方面的考慮,結果眼看着訪問量一天天網上爬,可突然發現有一天網站因爲訪問量過大而崩潰了,到時候哭都來不及。所以我們一定要未雨綢繆,在數據庫還 沒罷工前,想方設法給它減負,這也是這篇文章的主要議題。

大家都知道,當有一個request過來後,web服務器交給app服務器,app處理並從db中存取相關數據,但db存取的花費是相當高昂的。特 別是每次都取相同的數據,等於是讓數據庫每次都在做高耗費的無用功,數據庫如果會說話,肯定會發牢騷,你都問了這麼多遍了,難道還記不住嗎?是啊,如果 app拿到第一次數據並存到內存裏,下次讀取時直接從內存裏讀取,而不用麻煩數據庫,這樣不就給數據庫減負了?而且從內存取數據必然要比從數據庫媒介取快 很多倍,反而提升了應用程序的性能。

因此,我們可以在web/app層與db層之間加一層cache層,主要目的:1. 減少數據庫讀取負擔;2. 提高數據讀取速度。而且,cache存取的媒介是內存,而一臺服務器的內存容量一般都是有限制的,不像硬盤容量可以做到TB級別。所以,可以考慮採用分佈 式的cache層,這樣更易於破除內存容量的限制,同時又增加了靈活性。

3      Memcache工作原理

首先 memcached 是以守護程序方式運行於一個或多個服務器中,隨時接受客戶端的連接操作,客戶端可以由各種語言編寫,目前已知的客戶端 API 包括 Perl/PHP/Python/Ruby/Java/C#/C 等等。客戶端在與 memcached 服務建立連接之後,接下來的事情就是存取對象了,每個被存取的對象都有一個唯一的標識符 key,存取操作均通過這個 key 進行,保存到 memcached 中的對象實際上是放置內存中的,並不是保存在 cache 文件中的,這也是爲什麼 memcached 能夠如此高效快速的原因。注意,這些對象並不是持久的,服務停止之後,裏邊的數據就會丟失。

與許多 cache 工具類似,Memcached 的原理並不複雜。它採用了C/S的模式,在 server 端啓動服務進程,在啓動時可以指定監聽的 ip,自己的端口號,所使用的內存大小等幾個關鍵參數。一旦啓動,服務就一直處於可用狀態。Memcached 的目前版本是通過C實現,採用了單進程,單線程,異步I/O,基於事件 (event_based) 的服務方式.使用 libevent 作爲事件通知實現。多個 Server 可以協同工作,但這些 Server 之間是沒有任何通訊聯繫的,每個 Server 只是對自己的數據進行管理。Client 端通過指定 Server 端的 ip 地址(通過域名應該也可以)。需要緩存的對象或數據是以 key->value 對的形式保存在Server端。key 的值通過 hash 進行轉換,根據 hash 值把 value 傳遞到對應的具體的某個 Server 上。當需要獲取對象數據時,也根據 key 進行。首先對 key 進行 hash,通過獲得的值可以確定它被保存在了哪臺 Server 上,然後再向該 Server 發出請求。Client 端只需要知道保存 hash(key) 的值在哪臺服務器上就可以了。

       其實說到底,memcache 的工作就是在專門的機器的內存裏維護一張巨大的 hash 表,來存儲經常被讀寫的一些數組與文件,從而極大的提高網站的運行效率。

Memcached 介紹

Memcached是開源的分佈式cache系統,現在很多的大型web應用程序包括 facebook,youtube,wikipedia,yahoo等等都在使用memcached來支持他們每天數億級的頁面訪問。通過把cache層 與他們的web架構集成,他們的應用程序在提高了性能的同時,還大大降低了數據庫的負載。 具體的memcached資料大家可以直接從它的官方網站[1]上得到。這裏我就簡單給大家介紹一下memcached的工作原理:

Memcached處理的原子是每一個(key,value)對(以下簡稱kv對),key會通過一個hash算法轉化成hash-key,便於查找、對比以及做到儘可能的散列。同時,memcached用的是一個二級散列,通過一張大hash表來維護。

Memcached有兩個核心組件組成:服務端(ms)和客戶端(mc),在一個memcached的查詢中,mc先通過計算key的hash值來 確定kv對所處在的ms位置。當ms確定後,客戶端就會發送一個查詢請求給對應的ms,讓它來查找確切的數據。因爲這之間沒有交互以及多播協議,所以 memcached交互帶給網絡的影響是最小化的。

舉例說明:考慮以下這個場景,有三個mc分別是X,Y,Z,還有三個ms分別是A,B,C:

設置kv對 X想設置key=”foo”,value=”seattle” X拿到ms列表,並對key做hash轉化,根據hash值確定kv對所存的ms位置 B被選中了 X連接上B,B收到請求,把(key=”foo”,value=”seattle”)存了起來

獲取kv對 Z想得到key=”foo”的value Z用相同的hash算法算出hash值,並確定key=”foo”的值存在B上 Z連接上B,並從B那邊得到value=”seattle” 其他任何從X,Y,Z的想得到key=”foo”的值的請求都會發向B

Memcached服務器(ms)

內存分配

默認情況下,ms是用一個內置的叫“塊分配器”的組件來分配內存的。捨棄c++標準的malloc/free的內存分配,而採用塊分配器的主要目的 是爲了避免內存碎片,否則操作系統要花費更多時間來查找這些邏輯上連續的內存塊(實際上是斷開的)。用了塊分配器,ms會輪流的對內存進行大塊的分配,並 不斷重用。當然由於塊的大小各不相同,當數據大小和塊大小不太相符的情況下,還是有可能導致內存的浪費。

同時,ms對key和data都有相應的限制,key的長度不能超過250字節,data也不能超過塊大小的限制 --- 1MB。 因爲mc所使用的hash算法,並不會考慮到每個ms的內存大小。理論上mc會分配概率上等量的kv對給每個ms,這樣如果每個ms的內存都不太一樣,那 可能會導致內存使用率的降低。所以一種替代的解決方案是,根據每個ms的內存大小,找出他們的最大公約數,然後在每個ms上開n個容量=最大公約數的 instance,這樣就等於擁有了多個容量大小一樣的子ms,從而提供整體的內存使用率。

緩存策略

當ms的hash表滿了之後,新的插入數據會替代老的數據,更新的策略是LRU(最近最少使用),以及每個kv對的有效時限。Kv對存儲有效時限是在mc端由app設置並作爲參數傳給ms的。

同時ms採用是偷懶替代法,ms不會開額外的進程來實時監測過時的kv對並刪除,而是當且僅當,新來一個插入的數據,而此時又沒有多餘的空間放了,纔會進行清除動作。

緩存數據庫查詢 現在memcached最流行的一種使用方式是緩存數據庫查詢,下面舉一個簡單例子說明:

App需要得到userid=xxx的用戶信息,對應的查詢語句類似:

“SELECT * FROM users WHERE userid = xxx”

App先去問cache,有沒有“user:userid”(key定義可預先定義約束好)的數據,如果有,返回數據;如果沒有,App會從數據庫中讀取數據,並調用cache的add函數,把數據加入cache中。

當取的數據需要更新,app會調用cache的update函數,來保持數據庫與cache的數據同步。

從上面的例子我們也可以發現,一旦數據庫的數據發現變化,我們一定要及時更新cache中的數據,來保證app讀到的是同步的正確數據。當然我們可 以通過定時器方式記錄下cache中數據的失效時間,時間一過就會激發事件對cache進行更新,但這之間總會有時間上的延遲,導致app可能從 cache讀到髒數據,這也被稱爲狗洞問題。(以後我會專門描述研究這個問題)

數據冗餘與故障預防

從設計角度上,memcached是沒有數據冗餘環節的,它本身就是一個大規模的高性能cache層,加入數據冗餘所能帶來的只有設計的複雜性和提高系統的開支。

當一個ms上丟失了數據之後,app還是可以從數據庫中取得數據。不過更謹慎的做法是在某些ms不能正常工作時,提供額外的ms來支持cache,這樣就不會因爲app從cache中取不到數據而一下子給數據庫帶來過大的負載。

同時爲了減少某臺ms故障所帶來的影響,可以使用“熱備份”方案,就是用一臺新的ms來取代有問題的ms,當然新的ms還是要用原來ms的IP地址,大不了數據重新裝載一遍。

另外一種方式,就是提高你ms的節點數,然後mc會實時偵查每個節點的狀態,如果發現某個節點長時間沒有響應,就會從mc的可用server列表裏 刪除,並對server節點進行重新hash定位。當然這樣也會造成的問題是,原本key存儲在B上,變成存儲在C上了。所以此方案本身也有其弱點,最好 能和“熱備份”方案結合使用,就可以使故障造成的影響最小化。

Memcached客戶端(mc)

Memcached客戶端有各種語言的版本供大家使用,包括java,c,php,.net等等,具體可參見memcached api page[2]。 大家可以根據自己項目的需要,選擇合適的客戶端來集成。

緩存式的Web應用程序架構 有了緩存的支持,我們可以在傳統的app層和db層之間加入cache層,每個app服務器都可以綁定一個mc,每次數據的讀取都可以從ms中取得,如果 沒有,再從db層讀取。而當數據要進行更新時,除了要發送update的sql給db層,同時也要將更新的數據發給mc,讓mc去更新ms中的數據。

假設今後我們的數據庫可以和ms進行通訊了,那可以將更新的任務統一交給db層,每次數據庫更新數據的同時會自動去更新ms中的數據,這樣就可以進一步減少app層的邏輯複雜度。如下圖:

不過每次我們如果沒有從cache讀到數據,都不得不麻煩數據庫。爲了最小化數據庫的負載壓力,我們可以部署數據庫複寫,用slave數據庫來完成讀取操作,而master數據庫永遠只負責三件事:1.更新數據;2.同步slave數據庫;3.更新cache。如下圖:

以上這些緩存式web架構在實際應用中被證明是能有效並能極大地降低數據庫的負載同時又能提高web的運行性能。當然這些架構還可以根據具體的應用環境進行變種,以達到不同硬件條件下性能的最優化。


4.  安裝

然後,將memcached主程序文件安裝到服務器上。

Windows下安裝:

1.將上圖中Memcached 1.2.5.zip解壓縮到 D:\program files\memcached目錄下(此目錄自行定義)。

2.Ctrl+R,輸入cmd,打開命令行窗口,轉到D:\program files\memcached目錄下。

3.memcached.exe -d install

4.memcached.exe -d start

如果你要卸載,執行下面的命令:

1.memcached.exe -d stop

2.memcached.exe -d uninstall

Linux(CentOS 5.x)下安裝:

1. yum install gcc

2. cd /tmp

3. wget http://www.monkey.org/~provos/libevent-2.0.4-alpha.tar.gz   注:memcached 用到了 libevent 這個庫用於 Socket 的處理,所以 還需要安裝 libevent

4. tar zxvf libevent-2.0.4-alpha.tar.gz

5. cd libevent-2.0.4-alpha

6. ./configure -prefix=/usr/local/libevent

7. make

8. make install

9. cd ~

10. cd /tmp

11. http://memcached.googlecode.com/files/memcached-1.4.5.tar.gz

12. tar zxvf memcached-1.4.5.tar.gz

13. cd memcached-1.4.5

14. ./configure -prefix=/usr/local/memcached --with-libevent=/usr/local/libevent    注:安裝memcached時需要指定libevent的安裝位置

15. make

16. make install

17. export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/libevent/lib   注:將libevent的lib目錄加入LD_LIBRARY_PATH裏

18. vi /etc/sysconfig/iptables

19. 將下面這行加入進去

-A RH-Firewall-l-INPUT -p tcp -m tcp --dport 11211 -j ACCEPT  注:將memcached加入到防火牆允許訪問規則中

20. service iptables restart  注:防火牆重啓

21. /usr/local/memcached/bin/memcached -d   注:啓動memcached


memcached啓動參數描述:

-d :啓動一個守護進程,

-m:分配給Memcache使用的內存數量,單位是MB,默認是64MB,

-u :運行Memcache的用戶

-l  :監聽的服務器IP地址

-p :設置Memcache監聽的端口,默認是11211    注:-p(p爲小寫)

-c :設置最大併發連接數,默認是1024

-P :設置保存Memcache的pid文件   注:-P(P爲大寫)

如果要結束Memcache進程,執行:kill cat pid文件路徑


5      如何使用

  • 建立Manager類

Java代碼
  1. package com.alisoft.sme.memcached;  


  2. import java.util.Date;  


  3. import com.danga.MemCached.MemCachedClient;  

  4. import com.danga.MemCached.SockIOPool;  


  5. public class MemCachedManager {  


  6.    // 創建全局的唯一實例  

  7.    protected static MemCachedClient mcc = new MemCachedClient();  


  8.    protected static MemCachedManager memCachedManager = new MemCachedManager();  


  9.    // 設置與緩存服務器的連接池  

  10.    static {  

  11.        // 服務器列表和其權重  

  12.        String[] servers = { "127.0.0.1:11211" };  

  13.        Integer[] weights = { 3 };  


  14.        // 獲取socke連接池的實例對象  

  15.        SockIOPool pool = SockIOPool.getInstance();  


  16.        // 設置服務器信息  

  17.        pool.setServers(servers);  

  18.        pool.setWeights(weights);  


  19.        // 設置初始連接數、最小和最大連接數以及最大處理時間  

  20.        pool.setInitConn(5);  

  21.        pool.setMinConn(5);  

  22.        pool.setMaxConn(250);  

  23.        pool.setMaxIdle(1000 * 60 * 60 * 6);  


  24.        // 設置主線程的睡眠時間  

  25.        pool.setMaintSleep(30);  


  26.        // 設置TCP的參數,連接超時等  

  27.        pool.setNagle(false);  

  28.        pool.setSocketTO(3000);  

  29.        pool.setSocketConnectTO(0);  


  30.        // 初始化連接池  

  31.        pool.initialize();  


  32.        // 壓縮設置,超過指定大小(單位爲K)的數據都會被壓縮  

  33.        mcc.setCompressEnable(true);  

  34.        mcc.setCompressThreshold(64 * 1024);  

  35.    }  


  36.    /**

  37.     * 保護型構造方法,不允許實例化!

  38.     *  

  39.     */  

  40.    protected MemCachedManager() {  


  41.    }  


  42.    /**

  43.     * 獲取唯一實例.

  44.     *  

  45.     * @return

  46.     */  

  47.    public static MemCachedManager getInstance() {  

  48.        return memCachedManager;  

  49.    }  


  50.    /**

  51.     * 添加一個指定的值到緩存中.

  52.     *  

  53.     * @param key

  54.     * @param value

  55.     * @return

  56.     */  

  57.    public boolean add(String key, Object value) {  

  58.        return mcc.add(key, value);  

  59.    }  


  60.    public boolean add(String key, Object value, Date expiry) {  

  61.        return mcc.add(key, value, expiry);  

  62.    }  


  63.    public boolean replace(String key, Object value) {  

  64.        return mcc.replace(key, value);  

  65.    }  


  66.    public boolean replace(String key, Object value, Date expiry) {  

  67.        return mcc.replace(key, value, expiry);  

  68.    }  


  69.    /**

  70.     * 根據指定的關鍵字獲取對象.

  71.     *  

  72.     * @param key

  73.     * @return

  74.     */  

  75.    public Object get(String key) {  

  76.        return mcc.get(key);  

  77.    }  


  78.    public static void main(String[] args) {  

  79.        MemCachedManager cache = MemCachedManager.getInstance();  

  80.        cache.add("hello", 234);  

  81.        System.out.print("get value : " + cache.get("hello"));  

  82.    }  

  83. }  

建立數據對象

Java代碼
  1. package com.alisoft.sme.memcached;  


  2. import java.io.Serializable;  


  3. public class TBean implements Serializable {  


  4.    private static final long serialVersionUID = 1945562032261336919L;  


  5.    private String name;  


  6.    public String getName() {  

  7.        return name;  

  8.    }  


  9.    public void setName(String name) {  

  10.        this.name = name;  

  11.    }  

  12. }  

Java代碼
  1. <pre name="code" class="java"> </pre>  


  2. <h2 style="margin: 13pt 0cm 13pt 28.8pt;"><span style="" lang="EN-US"><span style=""><span style="font-family: 'Times New Roman';">   </span></span></span><span style=""><span style="font-size: large;">創建測試用例</span></span></h2>  

  3. <h2 style="margin: 13pt 0cm 13pt 28.8pt;"> </h2>  

  4. <pre name="code" class="java">package com.alisoft.sme.memcached.test;  


  5. import junit.framework.TestCase;  


  6. import org.junit.Test;  


  7. import com.alisoft.sme.memcached.MemCachedManager;  

  8. import com.alisoft.sme.memcached.TBean;  


  9. public class TestMemcached extends TestCase {  


  10.    private static MemCachedManager cache;  


  11.    @Test  

  12.    public void testCache() {  


  13.        TBean tb = new TBean();  

  14.        tb.setName("E網打進");  

  15.        cache.add("bean", tb);  


  16.        TBean tb1 = (TBean) cache.get("bean");  

  17.        System.out.println("name=" + tb1.getName());  

  18.        tb1.setName("E網打進_修改的");  


  19.        tb1 = (TBean) cache.get("bean");  

  20.        System.out.println("name=" + tb1.getName());  

  21.    }  


  22.    @Override  

  23.    protected void setUp() throws Exception {  

  24.        super.setUp();  

  25.        cache = MemCachedManager.getInstance();  

  26.    }  


  27.    @Override  

  28.    protected void tearDown() throws Exception {  

  29.        super.tearDown();  

  30.        cache = null;  

  31.    }  


  32. }  

  33. </pre>  

  34. <h2 style="margin: 13pt 0cm 13pt 28.8pt;"> <span style="">測試結果</span></h2>  

  35. <h2 style="margin: 13pt 0cm 13pt 28.8pt;"><span style="">  

  36. <pre name="code" class="java">[INFO] ++++ serializing for key: bean for class: com.alisoft.sme.memcached.TBean  

  37. [INFO] ++++ memcache cmd (result code): add bean 8 0 93 (NOT_STORED)  

  38. [INFO] ++++ data not stored in cache for key: bean  

  39. [INFO] ++++ deserializing class com.alisoft.sme.memcached.TBean  

  40. name=E網打進  

  41. [INFO] ++++ deserializing class com.alisoft.sme.memcached.TBean  

  42. name=E網打進  

  43. </pre>  

  44. </span></h2>  


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