memcached 常見問題

現階段正在研究memcached,心血來潮把memcached官方網站上的FAQ翻譯了一把,但願對想要使用memcached的同窗們有幫助。因爲兄弟我對數據庫不是很熟,有些關於數據庫概念的沒有翻譯,有些可能直接翻錯了,望你們指出。謝謝! php

1. 基本問題

1.1 什麼是 memcached ?

memcached 是一個高性能的分佈式內存的緩存系統。本質上它是通用的,但其目的是爲了加速動態 web 應用程序,減輕數據庫訪問壓力而設計的。 html

Danga Interfactive 開發了 memcached 用來提升 LiveJournal.com 網站的速度。這個網站由大批的 web 服務器和數據庫服務器構成,以此能夠爲其一百萬用戶提供天天兩千萬次的動態頁面訪問量。 Memcached 大大下降了數據庫的負擔,加快了頁面的加載時間,提升了資源利用效率,加快了緩存未命中狀況下的數據庫訪問時間。 node

1.2 哪裏能夠獲得 memcached ?

memcached 能夠從它的官方網站下載: http://www.danga.com/memcached/download.bml . python

1.3 怎樣安裝 memchched ?

能夠參照 yanwenhan 的 memcached 安裝 : http://yanwenhan.iteye.com/blog/160891 . mysql

1.4 哪些環境能夠運行 memcached ?

隨便哪裏,只要你有空閒的內存就能夠! Memcached 跑在 linux , BSD , windows 上。它通常不多佔用 CPU 資源,這樣你就能夠運行在任何有空閒內存的地方了。 linux

1.5 爲何要運行 memcached ?

若是你有一個高流量的網站,大多數的訪問會形成數據庫高負荷的情況。這時 memcached 可以減輕你數據庫的壓力。 web

1.6 怎樣訪問 memcached ?

你通常都會用一個客戶端類庫去訪問一個或多個 memcached 服務器。 算法

參考 http://www.socialtext.net/memcached/index.cgi?clients sql

或者 http://danga.com/memcached/apis.bml 數據庫

上面的地址列出了現有的類庫,他們是用 Perl , C , C# , PHP , Python , Java , Ruby 和 Postgresql 對存儲過程以及觸發器實現的。

你也能夠本身開發客戶端類庫來實現 memecached 協議,相關協議文檔:http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt

1.7 怎樣把 memcached 看成數據庫使用?

若是你不想把 memcached 看成緩存使用,而想把它當作數據存儲來使用,那你因該直接使用數據庫。 MySQL 集羣也能夠提供相似 memcached 的功能(儘管不是那麼容易安裝)並支持 HA (高可靠性)數據存儲。

1.8 我能枚舉遍歷 memcached 服務器內的存儲項嗎?

不能。 memcached 不支持也不會計劃支持這一特性。由於這會相對減慢服務器的速度以及阻塞操做(比較memcached 正在執行的每樣操做)。如上所述, memcached 是緩存而不是數據庫。雖然 Tugela 是一個源於memcached 的系統,但它有點慢,由於它使用 memcached 的方式有點像數據庫。

固然, memcached 徹底是一個軟件,因此從某種角度說,最終的答案可能會是「能遍歷 memcached 」,可是,枚舉遍歷數據項將會減慢和阻塞服務器。對於開發或測試服務器來講,這不算是問題。但對 99.9% 真正的部署服務器來講,回答是「不能」。

2. 集羣構架問題

2.1 memcached 怎樣工做?

memcached 的神奇之處在於它的兩步驟哈希方法。它的行爲讓人以爲它好像是一張巨大的,經過鍵值對查找的哈希表。給定一個鍵( key ),存儲或取出任意數據。

當查找 memcached 時,客戶端首先參照整個 memcached 服務器列表計算出鍵的哈希值。一旦它選擇了一個memcached 服務器,客戶端就發起請求,服務器根據鍵值在內部查找對應的數據項。

好比,咱們有客戶端 1 , 2 , 3 和服務器 A , B , C :

客戶端 1 想要設置鍵 "foo" 值 "barbaz" 。客戶端 1 參照服務器列表( A , B , C )來哈希 "foo" ,根據 "foo" 的哈希值,最終選擇了服務器 B 。而後客戶端 1 直接鏈接服務器 B ,設置鍵 "foo" ,值 "barbaz" 。

下一步,客戶端 2 想要獲得鍵 "foo" 對應的值。客戶端 2 運行與客戶端 1 相同的客戶端類庫,而且使用相同的服務器列表( A , B , C )。計算獲得鍵 "foo" 相同的哈希值,這樣就知道了這個鍵在服務器 B 上。而後直接請求鍵 "foo" 獲得相應的值 "barbaz" 。

不一樣的客戶端實如今 memcached 服務器內存儲數據的方式不一樣。一些客戶端實現的哈希算法也不同,但服務器端老是相同。

最後, memcached 自己被實現爲基於事件的,非阻塞的服務器。這是一種用來解決 C10K 問題而且能夠調整爲飢餓方式( scale like crazy )的框架。

以上這些最大的好處是什麼?

仔細閱讀上述條目( memcached 怎樣工做?)。在應付大系統時, memcached 最大的好處是其擁有的擴展能力。由於客戶端作了哈希計算,咱們徹底能夠把許多 memcached 節點加入到集羣之中。集羣的節點之間沒有會致使過載的相互鏈接,也沒有會引發向心聚爆( implode )的多點傳送協議。 It Just Works. Run out of memory? Add a few more nodes. Run out of CPU? Add a few more nodes. Have some spare RAM here and there? Add nodes!

2.2 memcached 的緩存策略是什麼?

memcached 的緩存結構是 LRU (最近最少使用)加上到期失效策略。當你在 memcached 內存儲數據項時,你可能會聲明它在緩存的失效時間。能夠是永久或是存活到將來某一時段。若是 memcached 服務器用完分配的內存,失效的數據被首先替換,而後是最近未使用的數據。

2.3 memcached 的冗餘是怎樣實現的?

沒有冗餘!很驚訝! memcached 是你應用的緩存層。在設計上它沒有任何的數據冗餘的概念。若是一個節點丟失了它的數據,你能夠從新從數據源獲取全部數據。你的應用可以在丟失 memcached 實例的狀況繼續運行,這一點尤爲要注意。 Don't write awful queries and expect memcached to be a fix-all! If you're worried about having too much of a spike in database usage during failure, you have some options. You can add more nodes (lessen impact of losing one), hotspares (take over IP address when down), etc.

2.4 memcached 是怎樣應對失效轉移的?

沒有應對! :) 在 memcached 節點失效時,集羣根本不作任何有關失敗轉移的事情。應對的行爲徹底取決於用戶。當節點失效時,下面有幾種方案供你選擇。

       2.4.1 忽略它!在失效節點恢復或被替換前,你有許多節點能夠應對這個節點失效所帶來的影響。

       2.4.2 從服務器列表中移除失效的節點。千萬當心!默認狀況下,客戶端增長或刪除服務器列表會讓你的緩存失效!由於用來作哈希參照的服務器列表已經改變,大多數鍵可能會被哈希出不一樣的值而被定爲到不一樣的服務器。能夠在同一時間重啓你全部的節點來恢復。

       2.4.3 能夠用相同 IP 地址的節點替換失效的節點。這樣能夠防止哈希紊亂。

       2.4.4 使用一致的哈希算法來增長刪除集羣中的節點。參考其餘的哈希算法。

2.5 怎樣從 memcached 中導出和導入批量數據?

你不能這樣作! memcached 是一個咱們稱之爲非阻塞服務器。 memcached 必定會十分仔細地考慮任何能致使服務器即刻暫停應答請求的功能。一般狀況下,批量導入數據不會是你真正須要的!考慮一下若是你的數據在導出導入中有所改變的話,你處理的就是髒數據了。另外就是你怎樣管理在數據導入期間過時的數據。

因此,導入導出功能並不像你一般想的那樣有用。但有個場景它變得十分有用。若是你有大量的不變的數據,導入導出數據可能會有幫助。雖然這根本不是典型的應用場景,但它倒是常常發生,因此這個特性可能會在未來出現。

Steven Grimm 給了個很好的例子:

http://lists.danga.com/pipermail/memcached/2007-July/004802.html

2.6 memcached 的驗證機制是怎樣工做的?

沒有驗證機制! memcached 位於你應用的下層。徹底沒有驗證機制的部分緣由是客戶端和服務器端輕量化。這樣創建新的鏈接會很快,也沒有服務器端的配置。

若是不想要嚴格控制訪問,你可使用防火牆,或者能夠經過 unix 的 domain socket 來爲 memcached 監聽。 

2.7 什麼是 memcached 的線程?爲何我要用它們?

線程規則!感謝 Steven Grimm 和 Facebook , memcached 1.2 以及更高的本版擁有線程的操做模式。在這兒我不會過多地涉及細節,由於我可能會弄錯。線程系統容許 memcached 利用多核 CPU 以及在他們之間共享緩存。它應用一個十分簡單的鎖機制來控制某些值須要被更新時的同步問題。對比在一臺物理機器上運行多個節點實例,線程模式可使多核 CPU 變的更加有效。

若是你沒有出現重負荷的狀況,你可能不用去配置線程。若是你用龐大的硬件運行一個龐大的 web 網站,你可能就會看見好處了。

2.8 在使用 memcached 時,我可能會碰到什麼限制?

你可能會看到的最簡單的限制是對鍵以及數據項的大小限制。鍵被限制在 250 字符以內。數據項不能超過 1M ,由於這是最大的塊( slab )值。

2.9 我能在多個服務器上使用不一樣大小的緩存嗎? memcached 會有效地利用大內存的服務器嗎?

memcached 的哈希算法決定了鍵存儲在哪臺服務器上,它不會去考慮服務器的內存大小。 But a workaround may be to run multiple memcached instances on your server with more memory with each instance using the same size cache as all your other servers.

2.10 什麼是二進制協議?我應該關注嗎?

這個問題的最佳信息在郵件列表上: http://lists.danga.com/pipermail/memcached/2007-July/004636.html

寫這篇文章的時候,這個問題還沒被解決,沒有客戶端被髮布。

二進制協議是一種高效的,可信賴的 c/s 協議用來加速 CPU 時間。從 Facebook 的測試來看,解析 ASCII 協議是memcached 對 CPU 時間的最大消耗。因此爲何不去提升它呢? :)

2.11 memcached 的內存分配是怎樣工做的?爲何不用 malloc 或 free 呢?爲何要用 slabs ?

事實上,這是編譯時的選項。默認狀況下 memcached 用內部的 slab 做爲分配器的。你真的很須要使用內建的slab 分配器。一開始, memcached 的確用 malloc/free 分配全部東西。然而,這並不能同操做系統的內存管理器很好地工做。你的操做系統花在查找連續內存塊用以 malloc() 的時間超出了 memcached 自己的操做運行時間。

slab 分配器就是用來解決這個問題的。在 memcached 內部以塊爲單位來分配和重用內存。由於內存被分爲不一樣大小的 slab ,若是你的數據項沒有徹底符合服務器選擇的 slab 的大小,這的確會致使浪費內存。 Steven Grimm已經在這方面作了至關有效的改進。

一些對 slab 的改進和折中方案在郵件列表中: http://lists.danga.com/pipermail/memcached/2006-May/002163.html

http://lists.danga.com/pipermail/memcached/2007-March/003753.html

若是你試圖用 malloc/free 分配內存,你能夠在構建中定義 'USE_SYSTEM_MALLIC' 。它可能沒有通過很好測試,因此不用期望能獲得開發者的支持。

3. 性能問題

3.1 爲何 memcached 沒有個人數據庫快?

在一對一的比較中, memcached 可能沒有你的 SQL 查詢快。然而,這並非它的目標。 memcached 的目標是可伸縮性。隨着鏈接和請求的增長, memcached 將會表現出比單獨的數據庫解決方案更好的性能。在決定memcached 不適合你的應用以前,請將你的代碼放在高併發的環境中測試。

4. 客戶端類庫

4.1 我能用不一樣的客戶端獲得相同的數據嗎?

技術上是能夠的,可是有些問題你可能會碰到:

       4.1.1 不一樣的類庫可能採用不一樣的方式序列號數據,好比, Perl 的 Cache::Memcached 會用 Storable 序列化複雜結構對象。其餘語言的客戶端極可能不能讀出這些格式的數據,你可能會考慮用簡單的 String 格式序列化對象,這須要外部類庫好比 JSON 和 XML 。

       4.1.2 一樣,你的數據從某個客戶端過來的時候可能被壓縮了,但另外一個客戶端卻沒有壓縮。

       4.1.3 不一樣的類庫可能採用不一樣的哈希算法。若是你正在鏈接多個服務器,你的鍵極可能被不一樣語言的客戶端相應地哈希後並存儲。不一樣語言的客戶端可能採用不一樣的策略來選擇服務器進行存儲,因此對同一個鍵來講, Perl的客戶端可能選擇服務器 A ,而 Python 客戶端選擇服務器 B 。 Perl 的 API 還容許你對不一樣的服務器設置不一樣的權重,這也多是形成這一問題的因素。

4.2 什麼是一致的哈希客戶端?

參考 http://www.last.fm/user/RJ/journal/2007/04/10/392555

5. 哈希 / 鍵分佈

5.1 數據項到期失效,何時失效的數據項會從緩存中刪除?

memcached 使用懶失效。當客戶端請求數據項時, memcached 在返回數據前會檢查失效時間來肯定數據項是否已經失效。

一樣地,當添加一個新的數據項時,若是緩存已經滿了, memcached 就會先替換失效的數據項,而後纔是緩存中最少使用的數據項。

6. 命名空間

6.1 memcached 不支持命名空間。然而有幾種選擇能夠模仿他們。

6.1.1 用鍵的前綴模仿命名空間,在鍵以前加入有意義的前綴。

6.1.2 用命名空間刪除數據項

儘管 memcached 不支持使用任何類型的通配符或命名空間來完成刪除操做,但有一些技巧模擬他們。

// 在 PHP 中使用一個叫 foo 的命名空間:
$ns_key = $memcache->get("foo_namespace_key");

// if not set, initialize it
if($ns_key===false) $memcache->set("foo_namespace_key", rand(1, 10000));
$my_key = "foo_".$ns_key."_12345";

// 清除命名空間:
$memcache->increment("foo_namespace_key");

7. 應用設計

7.1 在設計應用時,針對緩存有哪些東西是我應該考慮的?

7.1.1 緩存簡單的查詢結果

查詢緩存存儲了給定查詢語句對應的整個結果集。它最合適緩存那些常常被用到,但不會改變的 SQL 語句,好比載入特定的過濾內容。

$key = md5('SELECT * FROM rest_of_sql_statement_goes_here');

if ($memcache->get($key)) {
    return $memcache->get($key);`
} else {
    // Run the query and transform the result data into your final dataset form`
    $result = $query_results_mangled_into_most_likely_an_array`
    $memcache->set($key, $result, TRUE, 86400); // Store the result of the query for a day`
    return $result;`
}

記住,若是查詢語句對應的結果集改變,該結果集不會展示出來。這種方法不老是有用,但它確實讓工做變得比較快。

7.1.2 緩存簡單的基於行的查詢結果

基於行的緩存會檢查緩存數據的標識符列表,那些在緩存中的行能夠直接取出,不在緩存中的行將會從數據庫中取出並以他們本身的鍵緩存他們,最後加入到最終的數據集中返回。隨着時間的過去,大多數數據都會被緩存,這也意味着相比與數據庫,查詢語句會更多地從 memcached 中獲得數據行。若是數據是至關靜態的,咱們能夠設置一個較長的緩存時間。基於行的緩存模式對下面這種搜索狀況特別有用:數據集自己很大或是數據集是從多張表中獲得,而數據集取決於查詢的輸入參數可是查詢的結果集之間的有重複部分。

好比,若是你有用戶 A , B , C , D , E 的數據集。

你去點擊一張顯示用戶 A , B , E 信息的頁面。首先, memcached 獲得 3 個不一樣的鍵,每一個對應一個用戶去緩存中查找,所有未命中。而後就到數據庫中用 SQL 查詢獲得 3 個用戶的數據行,並緩存他們。

如今,你又去點擊另外一張顯示顯示 C , D , E 信息的頁面。當你去查找 memcached 時, C , D 的數據並無被命中,但咱們命中了 E 的數據。而後從數據庫獲得 C , D 的行數據,緩存在 memcached 中。

至此之後,不管這些用戶信息怎樣地排列組合,任何關於 A , B , C , D , E 信息的頁面均可以從 memcached獲得數據了。

Action flood control

Flood control is the process of throttling user activity, usually for load management. We first try to add a memcache key that uniquely identifies a user and times out after a given interval. If that succeeds, there is no identical key, and thus the user should be allowed to do the action. If the add fails, the user is still in the flood control interval, so shouldn't be allowed to continue their action. If all else fails and the key cannot be added or retrieved, something's wonky with memcache and it's up to you to decide whether to allow action or not (suggested yes to prevent long term memcache issues from stopping all actions).

So, if user A makes a comment in thread 7, and you don't want them to be able to comment again for another 60 seconds:

'add' a key (eg) 'noflood:A:7' into memcached. If you get a SUCCESS, the user may post. If you get a NOT_STORED (but not an error!), the key still exists and the user should be warned.

Note you may also try fetching a key and doing incr/decr on it if a user should only be allowed to perform an action a certain number of times before being throttled.

7.1.3 緩存的不是 SQL 數據

當你第一次緩存你手頭除了 SQL 之外的其餘結果集時,你可能並無在乎。是的,你能夠也應該存儲其餘的數據。

若是你正在構建一張顯示用戶信息的頁面,你可能獲得一段關於用戶的信息(姓名,生日,家庭住址,簡介)。而後你可能會將 XML 格式的簡介信息轉化爲 HTML 格式,或作其餘的一些工做。相比單獨存儲這些屬性,你可能更願意存儲通過渲染的數據塊。那時你就能夠簡單地取出被預處理後的 HTML 直接填充在頁面中,這樣節省了寶貴的 CPU 時間。

7.2 使用分層的緩存

不少時候你可使用局部緩存。咱們知道 memcached 能夠高速處理大量的緩存數據,可是有時你仍是要考慮維護多層的緩存結構。

Peter Zaitsev 已經寫了有關本地運行 PHP 的 APC 和本地運行 memcached 的速度比較,使用二者的好處請參考

http://www.mysqlperformanceblog.com/2006/08/09/cache-performance-comparison/

http://www.mysqlperformanceblog.com/2006/09/27/apc-or-memcached/

通常比只有少許數據(產品分類,鏈接信息,服務器狀態變量,應用配置變量),這些信息幾乎每頁都會訪問。緩存他們來讓他們儘量接近處理器是有意義的 , 這能夠幫助減小生成頁面的時間,而且在 memcached 失效的狀況下能夠增長可靠性。

7.3 當你的數據更新時更新你的緩存

你能夠作的一個很重要的提升,它能夠確保你的緩存無縫地集成到你的應用中去,它就是在數據庫數據更新時同步緩存的數據。

用戶 A 編輯了他的用戶信息。當他保存信息到數據庫時,你可能須要更新緩存中的數據,或是簡單地刪除老的用戶信息。若是你立刻更新數據,你要防止從數據庫讀取那些剛剛更新過的數據。當用戶習慣性地從新載入他們的用戶信息來確認是否修改爲功時,數據將從緩存中直接取出,這時他們得到了最新的數據。

這很了不得,由於沒有用戶想看過時的數據,不是嗎?

7.4 條件競爭和陳舊的數據

當你設計須要緩存數據的應用時,怎樣處理條件競爭和陳舊的數據變的很重要。

假設你緩存了顯示在邊條( sidebar )上最近 5 條評論,你決定每一分鐘刷新一次數據。然而,你忘記了邊條每秒被刷新 50 次 ! 所以,一旦 60 秒過去,當即就會有 10 多個進程執行相同的 SQL 查詢來重裝緩存內的數據。每當緩存內容失效時,就會致使一堆的 SQL 查詢操做。

更糟的是,你可能有多個進程在更新相同的數據,其中有一個進程更新了錯誤的數據。這時你就有陳舊的過時的數據要處理了。

組裝和重組緩存中的數據時,須要提醒的是檢查 memcached ,獲取 SQL ,緩存數據,這些操做根本不在一個原子內。

How to prevent clobbering updates, stampeding requests

So how does one prevent clobbering your own updates or stampeding during a cache miss? The easiest answer is to avoid the problem. Don't set caches to expire, and update them via cron, or as data is updated. This does not eliminate the possibility of a stampede, but removes it from becoming the norm.

Some great ideas from the mailing list also underline another approach:

If you want to avoid a stampede if key A expires for its common case (a timeout, for example). Since this is caused by a race condition between the cache miss, and the amount of time it takes to re-fetch and update the cache, you can try shortening the window.

First, set the cache item expire time way out in the future. Then, you embed the "real" timeout serialized with the value. For example you would set the item to timeout in 24 hours, but the embedded timeout might be five minutes in the future.

Then, when you get from the cache and examine the timeout and find it expired, immediately edit the embedded timeout to a time in the future and re-store the data as is. Finally, fetch from the DB and update the cache with the latest value. This does not eliminate, but drastically reduces the amount of time where a stampede can occur.

A decent python example can be found here: http://www.djangosnippets.org/snippets/155/

If you have a lot of data excelling at causing this problem, you might also consider using MySQL Cluster for it, or a tiered caching approach

Another (pretty cool!) idea is to use Gearman, as noted on the mailing list:http://lists.danga.com/pipermail/memcached/2007-July/004858.html

7.5 模擬帶鎖的添加命令

若是你實在須要鎖,你能夠經過「添加」命令模仿鎖的功能。儘管在未命中的狀況下它不是那麼有用,但若是你用它緩存日常的數據(應用服務器池的元數據)那仍是有用的。

好比,你要更新鍵 A 。

       7.5.1 添加一個 "lock:A" 的鍵,這個鍵有一個持續幾秒的過時時間(足夠長以使你能完成計算和更新,也不要很長,由於若是鎖進程掛了,這個鍵不會當即釋放)

       7.5.2 若是添加操做成功了,你就擁有了鎖:

              從緩存獲取鍵 A 的數據。

              在客戶端更改數據。

              更新緩存鍵 A 的數據。

              刪除鍵 "lock:A" ,若是你不須要當即再次更新,就讓它存活直到失效。

     7.5.3 若是添加操做失敗,說明有人獲取了鎖。這時讓應用作些合適的事,好比返回老數據,等待後重試,或是其餘的。

以上這些操做相似 MySQL 將 GET_LOCK 的 timeout 值設置成 0 。沒有辦法在 memcached 中經過互斥鎖模擬GET_LOCK() 的 timeout 操做。

7.6 預熱你的緩存

若是你有一個很高訪問率的站點,而且你正想加入故障恢復功能或是其餘全新的功能,你最終可能會碰到空緩存的問題。一開始緩存是空的,而後一大羣人點擊你的站點,在填充緩存的過程當中,你的數據庫可能會承受不住壓力。爲了解決這一問題,你能夠試試任何可行的方法來 " 溫暖 " 你的數據庫。

你能夠寫一些腳原本緩存通用的頁面。你也能夠寫一個命令行工具來填充緩存。兩種方法均可能對你有幫助。你能夠在高峯時刻在緩存裏填充一些內容。

相關文章
相關標籤/搜索