Memcached詳解
目錄
Memcached介紹
Memcached是什麼?
Free & open source, high-performance, distributed memory object caching system(自由&開放原始碼,高效能,分散式的記憶體物件快取系統)。由LiveJournal旗下的danga公司開發的老牌nosql應用。
什麼是NoSQL?
NoSQL,指的是非關係型的資料庫。
相對於傳統關係型資料庫的"行與列",NoSQL的鮮明特點為key-value儲存(memcache,redis),或基於文件儲存(mongodb)。
注:nosql --not only sql,不僅僅是關係型資料庫
Memcached安裝
Linux下編譯Memcached
準備編譯環境
再Linux下編譯,需要gcc,make,cmake,autoconf,libtool等工具,這幾件工具,以後還要編譯redis等使用,所以需要先安裝。在Linux系統聯網後,用如下命令安裝
yum install gcc gcc-c++ make cmake autoconf libtool
編譯Memcached
Memcached依賴於libevent庫,因此我們需要先安裝libevent。分別到libevent.org和memcached.org下載最新的stable版本(穩定版)。
先編譯libevent,再編譯memcached。
編譯Memcached時要指定libevent的路徑。
過程如下:假設原始碼在/root/package下,安裝在/usr/local下
tar zxvf libevent-2.1.8-stable.tar.gz cd libevent-2.1.8-stable ./configure --prefix=/usr/local/libevent make && make install 安裝memcached tar zxvf memcached-1.5.1.tar.gz cd memcached-1.5.1 ./configure --prefix=/usr/local/memcached --with-libevent=/usr/local/libevent/ make && make install 配置環境變數 vi /etc/profile export PATH="$PATH:/usr/local/memcached/bin" source /etc/profile 建立memcached使用者 useradd memcached 設定開機自動啟動: vi /etc/rc.local 增加 /usr/local/memcached/bin/memcached -u memcached -m 64 &
PHP安裝Memcached擴充套件
http://pecl.php.net/package/memcache下載擴充套件包 wget https://pecl.php.net/get/memcache-2.2.7.tgz tar zxvf memcache-2.2.7.tgz cd memcache-2.2.7 phpize//執行phpize命令,phpize是PHP的工具,用來將PHP的擴充套件與PHP程式建立關聯 配置編譯安裝 ./configure && make && make install 修改php.ini vi /usr/local/php/lib/php.ini 在大約928行左右加上擴充套件配置 ;linux extension load extension=memcache.so 重啟Apache apachectl -k restart 在phpinfo裡可以查詢到memcache說明安裝成功
Memcached的啟動
memcached -m 64 -p 11211 -u nobody -d//-d表示後臺執行(也可以用&)
可以使用memcached -h檢視幫助來了解各個引數的意義。
Memcached基本使用
PHP操作Memcache
方法 | 方法說明 |
---|---|
connect() | 開啟一個memcached服務端連線 |
add() | 增加一個條目到快取伺服器 |
addServer() | 向連線池中新增一個memcache伺服器 |
increment() | 增加一個元素的值 |
decrement() | 減小一個元素的值 |
delete() | 從服務端刪除一個元素 |
flush() | 清洗(刪除)已經儲存的所有的元素 |
get() | 從服務端檢回一個元素 |
set() | 儲存資料到快取伺服器 |
replace() | 替換已經存在的元素的值 |
pconnect() | 開啟一個到伺服器的持久化連線 |
close() | 關閉memcache連線 |
PHP連線memcache服務
$memcache = new Memcache(); $memcache->connect("192.168.20.131", 11211); bool Memcache::set (string $key , mixed $var [, int $flag [, int $expire ]]) $key:要設定值的key $var:要儲存的值,字串和數值直接儲存,其他型別序列化後儲存。資料的最大長度為1M。 $flag: 使用MEMCACHE_COMPRESSED指定對值進行壓縮(使用zlib)。通常傳入0即可,表示不需要壓縮。 $expire:當前寫入快取的資料的失效時間。如果此值設定為0表明此資料永不過期。當時間小於30天時表示的是時間間隔,當時間大於30天表示時間戳 bool Memcache::add (string $key , mixed $var [, int $flag [, int $expire ]]) 說明:與set類似,僅僅可以執行新增操作,不能執行修改操作,當key已經存在時,則add失敗 bool Memcache::replace (string $key , mixed $var [, int $flag [, int $expire ]]) 說明:與set類似,僅僅可以執行替換操作,僅僅在key存在時才可以執行,當key不存在時,替換是失敗的 get($key [, $flag]) --- 獲取 獲取時, 有時需要設定第二個引數, flag標誌! 比如如果儲存時設定了第二個引數為壓縮儲存,那麼獲取時也需要傳遞壓縮儲存的引數。 increment($key, $num) --- 遞增 在原有值得基礎上增加,第二個引數不寫預設是1 decrement($key, $num) --- 遞減 在原有值得基礎減少, delete($key) --- 刪除 fulsh() --- 清空/重新整理 close() --- 關閉連線
PHP
Memcached的記憶體管理與刪除機制
記憶體的碎片化
如果用 c 語言直接 malloc, free 來向作業系統申請和釋放記憶體時,在不斷的申請和釋放過程中,形成了一些很小的記憶體片斷,無法再利用。這種空閒,但無法利用記憶體的現象,--稱為記憶體的碎片化。
slab allocator緩解記憶體碎片化
Memcached 用 slab allocator 機制來管理記憶體。
Slab allocator 原理:預告把記憶體劃分成數個 slab cl ass 倉庫。(每個 slab class 大小 1 M)各倉庫,切分成不同尺時的小塊(chunk).(圖 3.2)
需要存內容時,判斷內容的大小,為其選取合理的倉庫.

系統如何選擇合適的chunk?
Memcached 根據收到的資料的大小,選擇最適合資料大小的 chunk 組(slab class)(圖 3.3)。memcached 中儲存著 slab class 內空閒 chunk 的列表,根據該列表選擇空的 chunk,然後將資料緩存於其中。

警告:
如果有100byte的記憶體要存,但122大小的倉庫的chunk滿了
並不會尋找更大的,如144的倉庫來儲存,
而是把122倉庫的舊資料踢掉!詳見過期與刪除機制
固定大小的chunk帶來的記憶體浪費
由於 slab allocator 機制中,分配的 chunk 的大小是”固定”的,因此,對於特定的 item,可能造成記憶體空間的浪費。
比如,將 100 位元組的資料快取到 122 位元組的 chunk 中,剩餘的 22 位元組就浪費了圖 3.4)

對於 chunk 空間的浪費問題,無法徹底解決,只能緩解該問題。
開發者可以對網站中快取中的 item 的長度進行統計,並制定合理的 slab class 中的 chunk 的大小。
可惜的是,我們目前還不能自定義 chunk 的大小,但可以通過引數來調整各 slab class 中 chunk 大小的增長速度。即增長因子,grow factor!
growfactor調優
Memcached 在啟動時可以通過- f 選項指定 Growth Factor 因子,並在某種程度上控制 slab 之間的差異。預設值為 1.25. 但是,在該選項出現之前,這個因子曾經固定為 2, 稱為”powers of2”策略。

對比可知,當 f=2 時,各 slab 中的 chunk size 增長很快,有些情況下就相當浪費記憶體。因此,我們應細心統計快取的大小,制定合理的增長因子。
注意:
當 f=1.25 時,從輸出結果來看,某些相鄰的 slab class 的大小比值並非為 1.25,可能會覺得有些 計算誤差,這些誤差是為了保持位元組數的對齊而故意設定的.
memcached的過期資料惰性刪除
1: 當某個值過期後,並沒有從記憶體刪除, 因此,stats 統計時, curr_item 有其資訊
2: 當某個新值去佔用他的位置時,當成空 chunk 來佔用.
3: 當 get 值時,判斷是否過期,如果過期,返回空,並且清空, curr_item 就減少了.
這個過期,只是讓使用者看不到這個資料而已,並沒有在過期的瞬間立即從記憶體刪除. 這個稱為 lazy expiration, 惰性失效.
好處:節省了CPU時間和檢測的成本
memcached的LRU刪除機制
如果以 122byte 大小的 chunk 舉例, 122 的 chunk 都滿了, 又有新的值(長度為 120)要加入, 要 擠掉誰?
memcached 此處用的 lru 刪除機制.
(作業系統的記憶體管理,常用 fifo,lru 刪除)
lru: least recently used 最近最少使用
fifo: first in ,first out
原理:當某個單元被請求時,維護一個計數器,通過計數器來判斷最近誰最少被使用. 就把誰t出.
注意:即使某個key是設定的永久有效期,也一樣會被踢出來!即-永久資料被踢現象
Memcached的一些引數限制
key 的長度: 250 位元組, (二進位制協議支援 65536 個位元組)
value 的限制: 1m, 一般都是儲存一些文字,如新聞列表等等,這個值足夠了.
記憶體的限制: 32 位下最大設定到 2G.
如果有 30g 資料要快取,一般也不會單例項裝 30G, (不要把雞蛋裝在一個籃子裡), 一般建議 開啟多個例項(可以在不同的機器,或同臺機器上的不同埠)
分散式叢集演算法
Memcached如何實現分散式
Memcached並不像MongoDB那樣,允許配置多個節點,且節點之間"自動分配資料"。也就是說,Memcached節點之間是不互相通訊的
因此,Memcached的分散式,要靠使用者去設計演算法,把資料分佈在多個Memcached節點中。
求餘/取模演算法
程式碼:
interface hasher { public function hash($str); } interface distribution { public function lookup($key); } /** * Class Moder * 求餘演算法 */ class Moder implements hasher, distribution { protected $server = array(); protected $num = 0; //計算一個字串對應的32 位迴圈冗餘校驗碼多項式 public function hash($str) { return sprintf("%u", crc32($str)); } //查詢資料應存放的節點伺服器 public function lookup($key) { $index = $this->hash($key) % $this->num; return $this->server[$index]; } //模擬增加一臺Memcached伺服器 public function addNode($s) { $this->server[] = $s; $this->num++; } //模擬Memcached伺服器宕機 public function delNode($s) { foreach ($this->server as $key => $value) { if($s == $value) { unset($this->server[$key]); } } $this->num--; $this->server = array_merge($this->server); //重新整理server的鍵,使其按照0->1->2遞增 } } $moder = new Moder(); $moder->addNode('a'); $moder->addNode('b'); $moder->addNode('c'); $moder->addNode('d'); for($i = 0; $i < 100; $i++) { $key = 'key_'.$i; echo $key, '---->', $moder->lookup($key), '<br>'; }
一致性雜湊演算法
通俗理解一致性雜湊:把各伺服器節點對映放在鐘錶的各個時刻上,把key也對映到鐘錶的某個時刻上,該key沿著鐘錶順時針走,碰到的第一個節點即為該key的儲存節點。如下圖所示:

一致性雜湊+虛擬節點演算法程式碼
interface hasher { public function hash($str); } interface distribution { public function lookup($key); } class Consistency implements hasher, distribution { protected $nodes = array(); protected $points = array(); protected $multi = 64;//每個Memcached伺服器對應的虛擬節點數量 //計算一個字串對應的32 位迴圈冗餘校驗碼多項式 public function hash($str) { return sprintf("%u", crc32($str)); } //查詢資料應存放的節點伺服器 public function lookup($key) { $position = $this->hash($key); reset($this->points);//重置指標(因為foreach遍歷時會陣列指標後移,等到下次遍歷陣列時,key()方法獲取的就不是第一個元素的鍵了) $needle = key($this->points); //預設會落在第一個節點 foreach ($this->points as $key => $value) { if($position <= $key) { $needle = $key; break; } } return $this->points[$needle]; } //新增節點 public function addNode($node) { $this->nodes[$node] = array(); for($i = 0; $i < $this->multi; $i++) { $point = $node . '_' . $i; $point = $this->hash($point);//虛擬節點轉為數字 $this->points[$point] = $node; $this->nodes[$node][] = $point; $this->resort(); } } public function delNode($node) { foreach ($this->nodes[$node] as $p) { //清除64個虛擬節點 unset($this->points[$p]); } unset($this->nodes[$node]); //去掉節點 } //對虛擬幾點進行排序 protected function resort() { ksort($this->points); } } $consistency = new Consistency(); $consistency->addNode('A'); $consistency->addNode('B'); $consistency->addNode('C'); $consistency->addNode('D'); echo $consistency->hash('name'); echo $consistency->lookup('name');
併發處理-樂觀鎖
在新版的memcached中,增加了對併發的控制,處理方案是:樂觀鎖
併發:多個程序(連線), 同時操作一個key. 就是併發操作.
樂觀鎖:
程序A, 先操作了快取項
在程序A第二次操作快取項前, 程序B操作了快取項.
之後, 程序A第二次操作快取項. 檢查, 在程序A第一次操作後, 是否有其他程序操作過需要的快取項. 如果有, 則放棄第二次操作. 採取樂觀的處理態度.(樂觀鎖定)
支援樂觀鎖的操作:
gets() 獲取
cas() 設定
注意:Memcache擴充套件還不支援這兩個操作,在telnet上可以演示
Memcached經典問題或現象
快取雪崩現象及真實案例
快取雪崩一般是由某個快取節點失效,導致其他節點的快取命中率下降,快取中缺失的資料去資料庫查詢。短時間內,造成資料庫伺服器崩潰。
重啟DB,短期又被壓垮,但快取資料也多一些。
DB反覆多次啟動,快取重建完畢,DB才穩定執行。
或者,是由於快取週期性的失效,比如每6個小時失效一次,那麼每6小時,將有一個請求“峰值”,嚴重者甚至會令DB崩潰。
解決辦法/方案:把快取設定為隨機3-9個小時的生命週期,這樣不同時失效,把工作分擔到各個時間點。
快取的無底洞現象multiget-hole
永久資料被踢現象
網上有人反饋為"memcached 資料丟失",明明設為永久有效,卻莫名其妙的丟失了.
其實,這要從 2 個方面來找原因:
即前面介紹的 惰性刪除,與 LRU 最近最少使用記錄刪除.
分析(如下圖)
1:如果 slab 裡的很多 chunk,已經過期,但過期後沒有被 get 過, 系統不知他們已經過期.
2:永久資料很久沒 get 了,不活躍,如果新增 item,則永久資料被踢了.
3: 當然,如果那些非永久資料被 get,也會被標識為 expire,從而不會再踢掉永久資料
