關於 memcached,雖然可以找到一些基本
介紹,DK大神也有點到一些進階議題,不過最近看了 Using memcached PDF 之後才真正學到了不少實戰技巧跟如何設計快取的概念。以下是一些零散的筆記:
裝好之後,基本的啟動方式是
* memcached -l 127.0.0.1 -P 11211 -m 128 -d
for deamon
* memcached -l 127.0.0.1 -P 11211 -m 128 -vv
for development debug
memcached 是一套 Name-Value Pair(NVP) 分散式記憶體快取系統,Key 的長度被限制在 250 characters,儲存的資料不能超過 1 megabyte。如果資料會超過 1mb,可以考慮使用壓縮工具,例如在 Rails 2.1 裡就內建了 ActiveSupport::Cache::CompressedMemCacheStore。
除了 memcached server,不同程式語言都有自己的 memcache client library 工具提供更方便的介面。一個基本的任務就是使用 Hashing algorithm 根據 Key 來決定該去存取一台 memcached server (如果有超過一台的 memcached server 的話)。Ruby 的 client 目前檯面上有幾套 1. memcache-client 2. fiveruns fork 版 memcache-client,針對 Hasing 的部份用C改寫了 3. 使用 libmemcached 的超快 memcached
有趣的是,不需要 client library,memcached 是可以直接 telnet 127.0.0.1 11211 的。telnet 之後打 stats 可以得到一些統計資料,除了目前共有多少筆資料跟共用多少空間之外,重要的有 cmd_get 跟 cmd_hits,就可以得出 cache hit ratio,這個數字應該努力到九成以上。另外還有你的 cmd_set 應該超過 cmd_get,
其他的 memcached 標準操作有 SET (新增或是更新一個值)、ADD(只有在該key不存在時,才會新增快取資料成功)、REPLACE(只有在該key已經存在時,才會更新資料成功)、GET(拿快取資料)。
在考慮使用 memcached 前,要知道它不是你系統中唯一的 cache,HTML 的整頁快取應該用 Web server、純 SQL query result 可以用 MySQL 內建 Query Cache,設定很簡單,效果很不錯 (Cache Performance Comparison。我自己的心得是很多時候你想要快取的物件其實並不是一個 SQL query 就可以搞定的,而是多個 SQL query 才計算出來,這時候去做純快取 SQL query result 我個人覺得也許不是很有意義,Rails 這部份就有人實做了query_memcached。。
另外要注意的是 memcached 並不是 persistent data store,只要一關掉 memcached server,裡面的資料就會通通不見,如果要拿來儲存 session authentication 資料要特別小心。
寫 memcached 程式的第一個問題是找出什麼資料需要快取? 一個常見的問題是我該快取 HTML fragment 還是純資料結構? 如果你操作介面只有一處用到,我們可以只快取 HTML fragment 即可,不然其實規模稍大的網站其實兩者都可以快取起來。
撰寫使用 memcached 程式的基本模式就是,先查看有沒有 key-value,有就把快取資料讀出來,沒有就運算結果後存到 memcached sever。這部份算是簡單的。真正困難的事情有兩件:一是清除過期的快取資料(expire),二是Key的命名。
命名的一個慣用的格式是 ObjectName:ObjectType:Key 或是該 SQL statement,但為了 Security 避免被人猜到 Key 和避免超過 255 bytes 的 key 長度限制,建議你將 Key hash 過。當然 Security 的最好解決方式是要有防火牆,因為只要連的上 Port 11211,有 Key 就可以讀的到快取資料,memcached server 本身是沒有任何認證機制的。
有兩種方式清除快取資料:一是在新增/刪除資料時,順便刪掉這個快取 key-value,這樣下次 request 來時便會重新 快取。二是在有更新的時候直接重設快取資料(Reset)。要注意的是如果您有不同的程式會直接更新資料庫(也就是不只是透過主應用程式,還有別的背景程式),就會有可能 memcachd 裡面的資料沒有被更新到,解法有 1. 清空所有資料 2. 有一隻程式可以重建 memcached 裡面的資料 3. 統一用一套知道 memcached 機制的介面操作
如果站很大,race condition 就會是個效能問題了,同時有多個 request 同時去更新同一份快取資料。雖然改用上述的方法二在有更新的時候直接重設(Reset)快取資料可以改善這個問題,不過如果資料根本還不在快取裡的話問題還是存在。也因此我們需要一個 lock 機制,概念是先檢查有沒有 lock entry,有的話先等一下,然後再抓。沒有的話就更新,然後刪除 lock entry。
實做招數有二,如果你的 client library 支援 ADD 操作(也就是如果該 key 已經存在的話,操作會失敗),就可以先用 ADD key:lock 決定是不是有別的 process 在用,沒有的話就 SET key,最後 DELETE delete key:lock。
如果 client library 只支援 GET 操作,只好先 GET key:lock,沒有值就 SET key:lock (有的話表示已經被鎖住,就先稍等),然後再設定真正的快取資料 SET key,最後 DELETE key:lock。這樣會比方法一多一個操作就是了。這的方法的範例程式碼可以去PDF的 Source Code下載,在 /LinuxBasics/memcachedUpdateCalendarOfEvents-3.pl。(well, it’s perl code)
如果不採用主動更新快取資料的方式,也可以直接設定過期時間(expire time),這取決於你有多常讀取/更新。越常更新 expire time 就越短。這種方式特別適合例如首頁每隔幾分鐘才換,而且沒有要求快取資料一定要跟資料庫裡面完全一致,例如在五分鐘內雖有很多更新資料,但前台只需要每隔五分鐘重組頁面即可。
為了徹底解決使用 expire time 方式仍會有的 race condition 問題,有一招是 proactive cache refill:我們另外紀錄一個比 expire 週期還短的 refresh time,如果 refresh time 到了,我們就先更新快取資料,這樣無論何時都不會有 client 要不到資料的情形,自然也就不會發生 rece condition。這招大絕的範例在 /LinuxBasics/memcachedRollingCalendarOfEvents.pl。
在更新跟查詢都很極端頻繁的情況下,也可以考慮用另外的程式專門去撈資料庫執行更新快取,這樣主應用程式只需要處理拿快取資料即可(唯一會去資料庫撈可能只有第一次資料不存在時)。
另一個議題是:如果群集跟個別資料都快取會有的重複現象,例如我們如果同時快取了 People.find(:all) 跟 Person.find(1),這時就重複存了 Person.find(1)。作者是建議群集的部份只當作指標(pointer)來使用,也就是群集只快取了所有 Person 的 ID 而不是完整資料。畢竟單項比較好重複使用(reuse),而且在處理更新快取資料時也比較方便,不會發生個別資料更新了,但是群集忘了沒有處理。
話說雖然 memcached 有提供 ADD/REPLACE 操作(根據Key存在或不存在,會導致儲存快取不一定成功的操作),但是這樣你就必須處理 error code 的情況,作者認為“快取”的基本原理是不回應(noreply):你不應該關心為什麼你的資料不在快取裡 (事實上就算你的 memcached server 都關掉,你的程式應該也要可以正常執行,只是比較慢而已),也許可能是 expire time 到了? 記憶體用完了? 被刪除了? 或是根本不存在? 這些都不重要。雖然有些例外,但基本上應該 SET 操作就可以應付絕大多數的情況。
很受用…^^
感謝整理如此好文
我看了幾篇文章, 裏面有提到 32bit OS 的限制, memory 只能用到 2G, 是否我裝了 win2008 server 64bit 版本, 就可以使用超過 2G 的 memory?
ps. 我目前安裝的是 1.2.6 的 32bit memcacheD
chainchung:
我沒有這麼多 memory 可以用,不清楚這個問題耶 :/