Free & open source, high-performance, distributed memory object caching system(自由&開放源碼,高性能,分佈式的內存對象緩存系統)
。由LiveJournal旗下的danga公司開發的老牌nosql應用。php
NoSQL,指的是菲關係型的數據庫。
相對於傳統關係型數據庫的"行與列",NoSQL的鮮明特色爲key-value存儲(memcache,redis),或基於文檔存儲(mongodb)。
注:nosql --not only sql,不只僅是關係型數據庫node
再Linux下編譯,須要gcc,make,cmake,autoconf,libtool等工具,這幾件工具,之後還要編譯redis等使用,因此須要先安裝。在Linux系統聯網後,用以下命令安裝
linux
yum install gcc gcc-c++ make cmake autoconf libtool
Memcached依賴於libevent庫,所以咱們須要先安裝libevent。分別到libevent.org和memcached.org下載最新的stable版本(穩定版)。
先編譯libevent,再編譯memcached。
編譯Memcached時要指定libevent的路徑。
過程以下:假設源碼在/root/package下,安裝在/usr/local下c++
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 &
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 -m 64 -p 11211 -u nobody -d //-d表示後臺運行(也能夠用&)
可使用memcached -h查看幫助來了解各個參數的意義。redis
選項 | 含義說明 |
---|---|
-p, --port=
|
TCP port to listen on (default: 11211)Memcached監聽的端口,要保證該端口號未被佔用 |
-U, --udp-port=
|
UDP port to listen on (default: 11211, 0 is off)指定監聽UDP的端口,默認11211,0表示關閉 |
-s, --unix-socket=
|
指定Memcached用於監聽的UNIX socket文件 |
-A, --enable-shutdown | |
-a, --unix-mask=
|
設置-s選項指定的UNIX socket文件的權限 |
-l, --listen=
|
監聽的服務器IP地址,若是有多個地址的話,使用逗號分隔,格式能夠爲「IP地址:端口號」,例如:-l 指定192.168.0.184:19830,192.168.0.195:13542;端口號也能夠經過-p選項指定 |
-d, --daemon | run as a daemon(指定memcached進程做爲一個守護進程啓動) |
-r, --enable-coredumps | 設置產生core文件大小 |
-u, --user=
|
assume identity of
|
-m, --memory-limit=
|
item memory in megabytes (default: 64 MB)指定分配給memcached使用的內存,單位是MB(默認是64M) |
-M, --disable-evictions | 當內存使用超出配置值時,禁止自動清除緩存中的數據項,此時Memcached不能夠,直到內存被釋放 |
-c, --conn-limit=
|
max simultaneous connections (default: 1024)設置最大運行的併發鏈接數,默認是1024 |
-k, --lock-memory | 設置鎖定全部分頁的內存,對於大緩存應用場景,謹慎使用該選項 |
-v, --verbose | 輸出警告和錯誤信息 |
-vv | 打印信息比-v更詳細:不只輸出警告和錯誤信息,也輸出客戶端請求和響應信息 |
-vvv | |
-h, --help | 查看幫助 |
-i, --license | 打印libevent和Memcached的licenses信息 |
-V, --version | |
-P, --pidfile=
|
保存memcached進程的pid文件 |
-f, --slab-growth-factor=
|
用於計算緩存數據項的內存塊大小的乘數因子,默認是1.25 |
-n, --slab-min-size=
|
爲緩存數據項的key、value、flag設置最小分配字節數,默認是48 |
-L, --enable-largepages | 嘗試使用大內存分頁(pages) |
-D
|
用於統計報告中Key前綴和ID之間的分隔符,默認是冒號「:」 |
-t, --threads=
|
指定用來處理請求的線程數,默認爲4 |
-R, --max-reqs-per-event | 爲避免客戶端餓死(starvation),對連續達到的客戶端請求數設置一個限額,若是超過該設置,會選擇另外一個鏈接來處理請求,默認爲20 |
-C, --disable-cas | 禁用CAS |
-b, --listen-backlog=
|
|
-B, --protocol=
|
指定使用的協議,默認行爲是自動協商(autonegotiate),可能使用的選項有auto、ascii、binary。 |
-I, --max-item-size=
|
覆蓋默認的STAB頁大小,默認是1M |
-F, --disable-flush-all | 禁用flush_all命令 |
-X, --disable-dumping | |
-o, --extended | 指定逗號分隔的選項,通常用於用於擴展或實驗性質的選項 |
方法 | 方法說明 |
---|---|
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() --- 關閉鏈接
若是用 c 語言直接 malloc, free 來向操做系統申請和釋放內存時,在不斷的申請和釋放過程當中,造成了一些很小的內存片段,沒法再利用。這種空閒,但沒法利用內存的現象,--稱爲內存的碎片化。sql
Memcached 用 slab allocator 機制來管理內存。
Slab allocator 原理:預告把內存劃分紅數個 slab cl ass 倉庫。(每一個 slab class 大小 1 M)各倉庫,切分紅不一樣尺時的小塊(chunk).(圖 3.2)
須要存內容時,判斷內容的大小,爲其選取合理的倉庫.mongodb
Memcached 根據收到的數據的大小,選擇最適合數據大小的 chunk 組(slab class)(圖 3.3)。memcached 中保存着 slab class 內空閒 chunk 的列表,根據該列表選擇空的 chunk,而後將數據緩存於其中。數據庫
警告:
若是有100byte的內存要存,但122大小的倉庫的chunk滿了
並不會尋找更大的,如144的倉庫來存儲,
而是把122倉庫的舊數據踢掉!詳見過時與刪除機制apache
因爲 slab allocator 機制中,分配的 chunk 的大小是」固定」的,所以,對於特定的 item,可能形成內存空間的浪費。
好比,將 100 字節的數據緩存到 122 字節的 chunk 中,剩餘的 22 字節就浪費了圖 3.4)
對於 chunk 空間的浪費問題,沒法完全解決,只能緩解該問題。
開發者能夠對網站中緩存中的 item 的長度進行統計,並制定合理的 slab class 中的 chunk 的大小。
惋惜的是,咱們目前還不能自定義 chunk 的大小,但能夠經過參數來調整各 slab class 中 chunk 大小的增加速度。即增加因子,grow factor!
Memcached 在啓動時能夠經過- f 選項指定 Growth Factor 因子,並在某種程度上控制 slab 之間的差別。默認值爲 1.25. 可是,在該選項出現以前,這個因子曾經固定爲 2, 稱爲」powers of2」策略。
對比可知,當 f=2 時,各 slab 中的 chunk size 增加很快,有些狀況下就至關浪費內存。所以,咱們應細心統計緩存的大小,制定合理的增加因子。
注意:
當 f=1.25 時,從輸出結果來看,某些相鄰的 slab class 的大小比值並不是爲 1.25,可能會以爲有些 計算偏差,這些偏差是爲了保持字節數的對齊而故意設置的.
1: 當某個值過時後,並無從內存刪除, 所以,stats 統計時, curr_item 有其信息
2: 當某個新值去佔用他的位置時,當成空 chunk 來佔用.
3: 當 get 值時,判斷是否過時,若是過時,返回空,而且清空, curr_item 就減小了.
這個過時,只是讓用戶看不到這個數據而已,並無在過時的瞬間當即從內存刪除. 這個稱爲 lazy expiration, 惰性失效.
好處:節省了CPU時間和檢測的成本
若是以 122byte 大小的 chunk 舉例, 122 的 chunk 都滿了, 又有新的值(長度爲 120)要加入, 要 擠掉誰?
memcached 此處用的 lru 刪除機制.
(操做系統的內存管理,經常使用 fifo,lru 刪除)
lru: least recently used 最近最少使用
fifo: first in ,first out
原理:當某個單元被請求時,維護一個計數器,經過計數器來判斷最近誰最少被使用. 就把誰t出.
注意:即便某個key是設置的永久有效期,也同樣會被踢出來!即-永久數據被踢現象
key 的長度: 250 字節, (二進制協議支持 65536 個字節)
value 的限制: 1m, 通常都是存儲一些文本,如新聞列表等等,這個值足夠了.
內存的限制: 32 位下最大設置到 2G.
若是有 30g 數據要緩存,通常也不會單實例裝 30G, (不要把雞蛋裝在一個籃子裏), 通常建議 開啓多個實例(能夠在不一樣的機器,或同臺機器上的不一樣端口)
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上能夠演示
緩存雪崩通常是由某個緩存節點失效,致使其餘節點的緩存命中率降低,緩存中缺失的數據去數據庫查詢。短期內,形成數據庫服務器崩潰。
重啓DB,短時間又被壓垮,但緩存數據也多一些。
DB反覆屢次啓動,緩存重建完畢,DB才穩定運行。
或者,是因爲緩存週期性的失效,好比每6個小時失效一次,那麼每6小時,將有一個請求「峯值」,嚴重者甚至會令DB崩潰。
解決辦法/方案:把緩存設置爲隨機3-9個小時的生命週期,這樣不一樣時失效,把工做分擔到各個時間點。
網上有人反饋爲"memcached 數據丟失",明明設爲永久有效,卻莫名其妙的丟失了.
其實,這要從 2 個方面來找緣由:
即前面介紹的 惰性刪除,與 LRU 最近最少使用記錄刪除.
分析(以下圖)
1:若是 slab 裏的不少 chunk,已通過期,但過時後沒有被 get 過, 系統不知他們已通過期.
2:永久數據好久沒 get 了,不活躍,若是新增 item,則永久數據被踢了.
3: 固然,若是那些非永久數據被 get,也會被標識爲 expire,從而不會再踢掉永久數據