Memcached

Memcached介紹

Memcached是什麼?

Free & open source, high-performance, distributed memory object caching system(自由&開放源碼,高性能,分佈式的內存對象緩存系統)
。由LiveJournal旗下的danga公司開發的老牌nosql應用。php

什麼是NoSQL?

NoSQL,指的是菲關係型的數據庫。

相對於傳統關係型數據庫的"行與列",NoSQL的鮮明特色爲key-value存儲(memcache,redis),或基於文檔存儲(mongodb)。

注:nosql --not only sql,不只僅是關係型數據庫node

Memcached安裝

Linux下編譯Memcached

準備編譯環境

再Linux下編譯,須要gcc,make,cmake,autoconf,libtool等工具,這幾件工具,之後還要編譯redis等使用,因此須要先安裝。在Linux系統聯網後,用以下命令安裝
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下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 &

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查看幫助來了解各個參數的意義。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 (運行memcached的用戶,不能夠以root用戶運行)
-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 指定逗號分隔的選項,通常用於用於擴展或實驗性質的選項

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() --- 關閉鏈接

Memcached的內存管理與刪除機制

內存的碎片化

若是用 c 語言直接 malloc, free 來向操做系統申請和釋放內存時,在不斷的申請和釋放過程當中,造成了一些很小的內存片段,沒法再利用。這種空閒,但沒法利用內存的現象,--稱爲內存的碎片化。sql

slab allocator緩解內存碎片化

Memcached 用 slab allocator 機制來管理內存。

Slab allocator 原理:預告把內存劃分紅數個 slab cl ass 倉庫。(每一個 slab class 大小 1 M)各倉庫,切分紅不一樣尺時的小塊(chunk).(圖 3.2)
須要存內容時,判斷內容的大小,爲其選取合理的倉庫.mongodb

系統如何選擇合適的chunk?

Memcached 根據收到的數據的大小,選擇最適合數據大小的 chunk 組(slab class)(圖 3.3)。memcached 中保存着 slab class 內空閒 chunk 的列表,根據該列表選擇空的 chunk,而後將數據緩存於其中。數據庫

警告:

若是有100byte的內存要存,但122大小的倉庫的chunk滿了

並不會尋找更大的,如144的倉庫來存儲,

而是把122倉庫的舊數據踢掉!詳見過時與刪除機制apache

固定大小的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,從而不會再踢掉永久數據

相關文章
相關標籤/搜索