這裏收集了經常被問到的關於memcached的問題
- memcached是怎麼工作的?
- memcached最大的優勢是什麼?
- memcached和MySQL的query cache相比,有什麼優缺點?
- memcached和服務器的local cache(比如PHP的APC、mmap文件等)相比,有什麼優缺點?
- memcached的cache機制是怎樣的?
- memcached如何實現冗餘機制?
- memcached如何處理容錯的?
- 如何將memcached中item批量導入導出?
- 但是我確實需要把memcached中的item都dump出來,確實需要把數據load到memcached中,怎麼辦?
- memcached是如何做身份驗證的?
- 如何使用memcached的多線程是什麼?如何使用它們?
- memcached能接受的key的最大長度是多少?(250bytes)
- memcached對item的過期時間有什麼限制?(爲什麼有30天的限制?)
- memcached最大能存儲多大的單個item?(1M byte)
- 爲什麼單個item的大小被限制在1M byte之內?
- 爲了讓memcached更有效地使用服務器的內存,可以在各個服務器上配置大小不等的緩存空間嗎?
- 什麼是binary協議?它值得關注嗎?
- memcached是如何分配內存的?爲什麼不用malloc/free!?究竟爲什麼使用slab呢?
- memcached能保證數據存儲的原子性嗎?
Memcached的神奇來自兩階段哈希(two-stage hash)。Memcached就像一個巨大的、存儲了很多<key,value>對的哈希表。通過key,可以存儲或查詢任意的數據。
客戶端可以把數據存儲在多臺memcached上。當查詢數據時,客戶端首先參考節點列表計算出key的哈希值(階段一哈希),進而選中一個節點;客戶端將請求發送給選中的節點,然後memcached節點通過一個內部的哈希算法(階段二哈希),查找真正的數據(item)。
舉個列子,假設有3個客戶端1, 2, 3,3臺memcached A, B, C:
Client 1想把數據"barbaz"以key "foo"存儲。Client 1首先參考節點列表(A, B, C),計算key "foo"的哈希值,假設memcached B被選中。接着,Client 1直接connect到memcached B,通過key "foo"把數據"barbaz"存儲進去。 Client 2使用與Client 1相同的客戶端庫(意味着階段一的哈希算法相同),也擁有同樣的memcached列表(A, B, C)。
於是,經過相同的哈希計算(階段一),Client 2計算出key "foo"在memcached B上,然後它直接請求memcached B,得到數據"barbaz"。
Client 1想把數據"barbaz"以key "foo"存儲。Client 1首先參考節點列表(A, B, C),計算key "foo"的哈希值,假設memcached B被選中。接着,Client 1直接connect到memcached B,通過key "foo"把數據"barbaz"存儲進去。 Client 2使用與Client 1相同的客戶端庫(意味着階段一的哈希算法相同),也擁有同樣的memcached列表(A, B, C)。
於是,經過相同的哈希計算(階段一),Client 2計算出key "foo"在memcached B上,然後它直接請求memcached B,得到數據"barbaz"。
各種客戶端在memcached中數據的存儲形式是不同的(perl Storable, php serialize, java hibernate, JSON等)。一些客戶端實現的哈希算法也不一樣。但是,memcached服務器端的行爲總是一致的。
最後,從實現的角度看,memcached是一個非阻塞的、基於事件的服務器程序。這種架構可以很好地解決C10K problem ,並具有極佳的可擴展性。
可以參考A Story of Caching ,這篇文章簡單解釋了客戶端與memcached是如何交互的。
請仔細閱讀上面的問題(即memcached是如何工作的)。Memcached最大的好處就是它帶來了極佳的水平可擴展性,特別是在一個巨大的系統中。由於客戶端自己做了一次哈希,那麼我們很容易增加大量memcached到集羣中。memcached之間沒有相互通信,因此不會增加 memcached的負載;沒有多播協議,不會網絡通信量爆炸(implode)。memcached的集羣很好用。內存不夠了?增加幾臺memcached吧;CPU不夠用了?再增加幾臺吧;有多餘的內存?在增加幾臺吧,不要浪費了。
基於memcached的基本原則,可以相當輕鬆地構建出不同類型的緩存架構。除了這篇FAQ,在其他地方很容易找到詳細資料的。
看看下面的幾個問題吧,它們在memcached、服務器的local cache和MySQL的query cache之間做了比較。這幾個問題會讓您有更全面的認識。
把memcached引入應用中,還是需要不少工作量的。MySQL有個使用方便的query cache,可以自動地緩存SQL查詢的結果,被緩存的SQL查詢可以被反覆地快速執行。Memcached與之相比,怎麼樣呢?MySQL的query cache是集中式的,連接到該query cache的MySQL服務器都會受益。
- 當您修改表時,MySQL的query cache會立刻被刷新(flush)。存儲一個memcached item只需要很少的時間,但是當寫操作很頻繁時,MySQL的query cache會經常讓所有緩存數據都失效。
- 在多核CPU上,MySQL的query cache會遇到擴展問題(scalability issues)。在多核CPU上,query cache會增加一個全局鎖(global lock), 由於需要刷新更多的緩存數據,速度會變得更慢。
- 在MySQL的query cache中,我們是不能存儲任意的數據的(只能是SQL查詢結果)。而利用memcached,我們可以搭建出各種高效的緩存。比如,可以執行多個獨立的查詢,構建出一個用戶對象(user object),然後將用戶對象緩存到memcached中。而query cache是SQL語句級別的,不可能做到這一點。在小的網站中,query cache會有所幫助,但隨着網站規模的增加,query cache的弊將大於利。
- query cache能夠利用的內存容量受到MySQL服務器空閒內存空間的限制。給數據庫服務器增加更多的內存來緩存數據,固然是很好的。但是,有了memcached,只要您有空閒的內存,都可以用來增加memcached集羣的規模,然後您就可以緩存更多的數據。
首先,local cache有許多與上面(query cache)相同的問題。local cache能夠利用的內存容量受到(單臺)服務器空閒內存空間的限制。不過,local cache有一點比memcached和query cache都要好,那就是它不但可以存儲任意的數據,而且沒有網絡存取的延遲。
- local cache的數據查詢更快。考慮把highly common的數據放在local cache中吧。如果每個頁面都需要加載一些數量較少的數據,考慮把它們放在local cached吧。
- local cache缺少集體失效(group invalidation)的特性。在memcached集羣中,刪除或更新一個key會讓所有的觀察者覺察到。但是在local cache中, 我們只能通知所有的服務器刷新cache(很慢,不具擴展性),或者僅僅依賴緩存超時失效機制。
- local cache面臨着嚴重的內存限制,這一點上面已經提到。
Memcached主要的cache機制是LRU(最近最少用)算法+超時失效。當您存數據到memcached中,可以指定該數據在緩存中可以呆多久Which is forever, or some time in the future。如果memcached的內存不夠用了,過期的slabs會優先被替換,接着就輪到最老的未被使用的slabs。
memcached如何實現冗餘機制?
不實現!我們對這個問題感到很驚訝。Memcached應該是應用的緩存層。它的設計本身就不帶有任何冗餘機制。如果一個memcached節點失去了所有數據,您應該可以從數據源(比如數據庫)再次獲取到數據。您應該特別注意,您的應用應該可以容忍節點的失效。不要寫一些糟糕的查詢代碼,寄希望於memcached來保證一切!如果您擔心節點失效會大大加重數據庫的負擔,那麼您可以採取一些辦法。比如您可以增加更多的節點(來減少丟失一個節點的影響),熱備節點(在其他節點down了的時候接管IP),等等。
不實現!我們對這個問題感到很驚訝。Memcached應該是應用的緩存層。它的設計本身就不帶有任何冗餘機制。如果一個memcached節點失去了所有數據,您應該可以從數據源(比如數據庫)再次獲取到數據。您應該特別注意,您的應用應該可以容忍節點的失效。不要寫一些糟糕的查詢代碼,寄希望於memcached來保證一切!如果您擔心節點失效會大大加重數據庫的負擔,那麼您可以採取一些辦法。比如您可以增加更多的節點(來減少丟失一個節點的影響),熱備節點(在其他節點down了的時候接管IP),等等。
memcached如何處理容錯的?
不處理!:) 在memcached節點失效的情況下,集羣沒有必要做任何容錯處理。如果發生了節點失效,應對的措施完全取決於用戶。節點失效時,下面列出幾種方案供您選擇:
不處理!:) 在memcached節點失效的情況下,集羣沒有必要做任何容錯處理。如果發生了節點失效,應對的措施完全取決於用戶。節點失效時,下面列出幾種方案供您選擇:
- 忽略它! 在失效節點被恢復或替換之前,還有很多其他節點可以應對節點失效帶來的影響。
- 把失效的節點從節點列表中移除。做這個操作千萬要小心!在默認情況下(餘數式哈希算法),客戶端添加或移除節點,會導致所有的緩存數據不可用!因爲哈希參照的節點列表變化了,大部分key會因爲哈希值的改變而被映射到(與原來)不同的節點上。
- 啓動熱備節點,接管失效節點所佔用的IP。這樣可以防止哈希紊亂(hashing chaos)。
- 如果希望添加和移除節點,而不影響原先的哈希結果,可以使用一致性哈希算法(consistent hashing)。您可以百度一下一致性哈希算法。支持一致性哈希的客戶端已經很成熟,而且被廣泛使用。去嘗試一下吧!
- 兩次哈希(reshing)。當客戶端存取數據時,如果發現一個節點down了,就再做一次哈希(哈希算法與前一次不同),重新選擇另一個節點(需要注意的時,客戶端並沒有把down的節點從節點列表中移除,下次還是有可能先哈希到它)。如果某個節點時好時壞,兩次哈希的方法就有風險了,好的節點和壞的節點上都可能存在髒數據(stale data)。
您不應該這樣做!Memcached是一個非阻塞的服務器。任何可能導致memcached暫停或瞬時拒絕服務的操作都應該值得深思熟慮。向memcached中批量導入數據往往不是您真正想要的!想象看,如果緩存數據在導出導入之間發生了變化,您就需要處理髒數據了;如果緩存數據在導出導入之間過期了,您又怎麼處理這些數據呢?
因此,批量導出導入數據並不像您想象中的那麼有用。不過在一個場景倒是很有用。如果您有大量的從不變化的數據,並且希望緩存很快熱(warm)起來,批量導入緩存數據是很有幫助的。雖然這個場景並不典型,但卻經常發生,因此我們會考慮在將來實現批量導出導入的功能。
Steven Grimm,一如既往地,,在郵件列表中給出了另一個很好的例子:http://lists.danga.com/pipermail/memcached/2007-July/004802.html 。
好吧好吧。如果您需要批量導出導入,最可能的原因一般是重新生成緩存數據需要消耗很長的時間,或者數據庫壞了讓您飽受痛苦。
如果一個memcached節點down了讓您很痛苦,那麼您還會陷入其他很多麻煩。您的系統太脆弱了。您需要做一些優化工作。比如處理"驚羣"問題(比如 memcached節點都失效了,反覆的查詢讓您的數據庫不堪重負...這個問題在FAQ的其他提到過),或者優化不好的查詢。記住,Memcached 並不是您逃避優化查詢的藉口。
如果您的麻煩僅僅是重新生成緩存數據需要消耗很長時間(15秒到超過5分鐘),您可以考慮重新使用數據庫。這裏給出一些提示:
- 使用MogileFS(或者CouchDB等類似的軟件)在存儲item。把item計算出來並dump到磁盤上。MogileFS可以很方便地覆寫item,並提供快速地訪問。您甚至可以把MogileFS中的item緩存在memcached中,這樣可以加快讀取速度。 MogileFS+Memcached的組合可以加快緩存不命中時的響應速度,提高網站的可用性。
- 重新使用MySQL。MySQL的InnoDB主鍵查詢的速度非常快。如果大部分緩存數據都可以放到VARCHAR字段中,那麼主鍵查詢的性能將更好。從memcached中按key查詢幾乎等價於MySQL的主鍵查詢:將key 哈希到64-bit的整數,然後將數據存儲到MySQL中。您可以把原始(不做哈希)的key存儲都普通的字段中,然後建立二級索引來加快查詢...key被動地失效,批量刪除失效的key,等等。
上面的方法都可以引入memcached,在重啓memcached的時候仍然提供很好的性能。由於您不需要當心"hot"的item被memcached LRU算法突然淘汰,用戶再也不用花幾分鐘來等待重新生成緩存數據(當緩存數據突然從內存中消失時),因此上面的方法可以全面提高性能。
關於這些方法的細節,詳見博客:http://dormando.livejournal.com/495593.html 。
memcached是如何做身份驗證的?
沒有身份認證機制!memcached是運行在應用下層的軟件(身份驗證應該是應用上層的職責)。memcached的客戶端和服務器端之所以是輕量級的,部分原因就是完全沒有實現身份驗證機制。這樣,memcached可以很快地創建新連接,服務器端也無需任何配置。
沒有身份認證機制!memcached是運行在應用下層的軟件(身份驗證應該是應用上層的職責)。memcached的客戶端和服務器端之所以是輕量級的,部分原因就是完全沒有實現身份驗證機制。這樣,memcached可以很快地創建新連接,服務器端也無需任何配置。
如果您希望限制訪問,您可以使用防火牆,或者讓memcached監聽unix domain socket。
memcached的多線程是什麼?如何使用它們?
線程就是定律(threads rule)!在Steven Grimm和Facebook的努力下,memcached 1.2及更高版本擁有了多線程模式。多線程模式允許memcached能夠充分利用多個CPU,並在CPU之間共享所有的緩存數據。memcached使用一種簡單的鎖機制來保證數據更新操作的互斥。相比在同一個物理機器上運行多個memcached實例,這種方式能夠更有效地處理multi gets。
線程就是定律(threads rule)!在Steven Grimm和Facebook的努力下,memcached 1.2及更高版本擁有了多線程模式。多線程模式允許memcached能夠充分利用多個CPU,並在CPU之間共享所有的緩存數據。memcached使用一種簡單的鎖機制來保證數據更新操作的互斥。相比在同一個物理機器上運行多個memcached實例,這種方式能夠更有效地處理multi gets。
如果您的系統負載並不重,也許您不需要啓用多線程工作模式。如果您在運行一個擁有大規模硬件的、龐大的網站,您將會看到多線程的好處。
簡單地總結一下:命令解析(memcached在這裏花了大部分時間)可以運行在多線程模式下。memcached內部對數據的操作是基於很多全局鎖的(因此這部分工作不是多線程的)。未來對多線程模式的改進,將移除大量的全局鎖,提高memcached在負載極高的場景下的性能。
memcached能接受的key的最大長度是多少?
key的最大長度是250個字符。需要注意的是,250是memcached服務器端內部的限制,如果您使用的客戶端支持"key的前綴"或類似特性,那麼key(前綴+原始key)的最大長度是可以超過250個字符的。我們推薦使用使用較短的key,因爲可以節省內存和帶寬。
key的最大長度是250個字符。需要注意的是,250是memcached服務器端內部的限制,如果您使用的客戶端支持"key的前綴"或類似特性,那麼key(前綴+原始key)的最大長度是可以超過250個字符的。我們推薦使用使用較短的key,因爲可以節省內存和帶寬。
memcached對item的過期時間有什麼限制?
過期時間最大可以達到30天。memcached把傳入的過期時間(時間段)解釋成時間點後,一旦到了這個時間點,memcached就把item置爲失效狀態。這是一個簡單但obscure的機制。
過期時間最大可以達到30天。memcached把傳入的過期時間(時間段)解釋成時間點後,一旦到了這個時間點,memcached就把item置爲失效狀態。這是一個簡單但obscure的機制。
memcached最大能存儲多大的單個item?
1MB。如果你的數據大於1MB,可以考慮在客戶端壓縮或拆分到多個key中。
1MB。如果你的數據大於1MB,可以考慮在客戶端壓縮或拆分到多個key中。
簡單的回答:因爲內存分配器的算法就是這樣的。
詳細的回答:Memcached的內存存儲引擎(引擎將來可插拔...),使用slabs來管理內存。內存被分成大小不等的slabs chunks(先分成大小相等的slabs,然後每個slab被分成大小相等chunks,不同slab的chunk大小是不相等的)。chunk的大小依次從一個最小數開始,按某個因子增長,直到達到最大的可能值。
如果最小值爲400B,最大值是1MB,因子是1.20,各個slab的chunk的大小依次是:slab1 - 400B slab2 - 480B slab3 - 576B ...
slab中chunk越大,它和前面的slab之間的間隙就越大。因此,最大值越大,內存利用率越低。Memcached必須爲每個slab預先分配內存,因此如果設置了較小的因子和較大的最大值,會需要更多的內存。
還有其他原因使得您不要這樣向memcached中存取很大的數據...不要嘗試把巨大的網頁放到mencached中。把這樣大的數據結構load和unpack到內存中需要花費很長的時間,從而導致您的網站性能反而不好。
如果您確實需要存儲大於1MB的數據,你可以修改slabs.c:POWER_BLOCK的值,然後重新編譯memcached;或者使用低效的malloc/free。其他的建議包括數據庫、MogileFS等。
我可以在不同的memcached節點上使用大小不等的緩存空間嗎?這麼做之後,memcached能夠更有效地使用內存嗎?
Memcache客戶端僅根據哈希算法來決定將某個key存儲在哪個節點上,而不考慮節點的內存大小。因此,您可以在不同的節點上使用大小不等的緩存。但是一般都是這樣做的:擁有較多內存的節點上可以運行多個memcached實例,每個實例使用的內存跟其他節點上的實例相同。
Memcache客戶端僅根據哈希算法來決定將某個key存儲在哪個節點上,而不考慮節點的內存大小。因此,您可以在不同的節點上使用大小不等的緩存。但是一般都是這樣做的:擁有較多內存的節點上可以運行多個memcached實例,每個實例使用的內存跟其他節點上的實例相同。
關於二進制最好的信息當然是二進制協議規範:http://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol 。
二進制協議嘗試爲端提供一個更有效的、可靠的協議,減少客戶端/服務器端因處理協議而產生的CPU時間。
根據Facebook的測試,解析ASCII協議是memcached中消耗CPU時間最多的環節。所以,我們爲什麼不改進ASCII協議呢?
根據Facebook的測試,解析ASCII協議是memcached中消耗CPU時間最多的環節。所以,我們爲什麼不改進ASCII協議呢?
在這個郵件列表的thread中可以找到一些舊的信息:http://lists.danga.com/pipermail/memcached/2007-July/004636.html 。
memcached的內存分配器是如何工作的?爲什麼不適用malloc/free!?爲何要使用slabs?
實際上,這是一個編譯時選項。默認會使用內部的slab分配器。您確實確實應該使用內建的slab分配器。最早的時候,memcached只使用malloc/free來管理內存。然而,這種方式不能與OS的內存管理以前很好地工作。反覆地malloc/free造成了內存碎片,OS最終花費大量的時間去查找連續的內存塊來滿足malloc的請求,而不是運行memcached進程。如果您不同意,當然可以使用malloc!只是不要在郵件列表中抱怨啊:)
實際上,這是一個編譯時選項。默認會使用內部的slab分配器。您確實確實應該使用內建的slab分配器。最早的時候,memcached只使用malloc/free來管理內存。然而,這種方式不能與OS的內存管理以前很好地工作。反覆地malloc/free造成了內存碎片,OS最終花費大量的時間去查找連續的內存塊來滿足malloc的請求,而不是運行memcached進程。如果您不同意,當然可以使用malloc!只是不要在郵件列表中抱怨啊:)
slab分配器就是爲了解決這個問題而生的。內存被分配並劃分成chunks,一直被重複使用。因爲內存被劃分成大小不等的slabs,如果item的大小與被選擇存放它的slab不是很合適的話,就會浪費一些內存。Steven Grimm正在這方面已經做出了有效的改進。
郵件列表中有一些關於slab的改進(power of n 還是 power of 2)和權衡方案:http://lists.danga.com/pipermail/memcached/2006-May/002163.htmlhttp://lists.danga.com/pipermail/memcached/2007-March/003753.html 。
如果您想使用malloc/free,看看它們工作地怎麼樣,您可以在構建過程中定義USE_SYSTEM_MALLOC。這個特性沒有經過很好的測試,所以太不可能得到開發者的支持。
memcached是原子的嗎?
當然!好吧,讓我們來明確一下:
所有的被髮送到memcached的單個命令是完全原子的。如果您針對同一份數據同時發送了一個set命令和一個get命令,它們不會影響對方。它們將被串行化、先後執行。即使在多線程模式,所有的命令都是原子的,除非程序有bug:)
命令序列不是原子的。如果您通過get命令獲取了一個item,修改了它,然後想把它set回memcached,我們不保證這個item沒有被其他進程(process,未必是操作系統中的進程)操作過。在併發的情況下,您也可能覆寫了一個被其他進程set的item。
當然!好吧,讓我們來明確一下:
所有的被髮送到memcached的單個命令是完全原子的。如果您針對同一份數據同時發送了一個set命令和一個get命令,它們不會影響對方。它們將被串行化、先後執行。即使在多線程模式,所有的命令都是原子的,除非程序有bug:)
命令序列不是原子的。如果您通過get命令獲取了一個item,修改了它,然後想把它set回memcached,我們不保證這個item沒有被其他進程(process,未必是操作系統中的進程)操作過。在併發的情況下,您也可能覆寫了一個被其他進程set的item。
memcached 1.2.5以及更高版本,提供了gets和cas命令,它們可以解決上面的問題。如果您使用gets命令查詢某個key的item,memcached會給您返回該item當前值的唯一標識。如果您覆寫了這個item並想把它寫回到memcached中,您可以通過cas命令把那個唯一標識一起發送給memcached。如果該item存放在memcached中的唯一標識與您提供的一致,您的寫操作將會成功。如果另一個進程在這期間也修改了這個item,那麼該item存放在memcached中的唯一標識將會改變,您的寫操作就會失敗。
通常,基於memcached中item的值來修改item,是一件棘手的事情。除非您很清楚自己在做什麼,否則請不要做這樣的事情。