memcache 的內存管理介紹和 php實現memcache一致性哈希分佈式算法

1 網絡IO模型

       安裝memcached需要先安裝libevent

  Memcached是多線程,非阻塞IO複用的網絡模型,分爲監聽主線程和worker子線程,監聽線程監聽網絡連接,接受請求後,將連接描述字 pipe 傳遞給worker線程,進行讀寫IO, 網絡層使用libevent封裝的事件庫,多線程模型可以發揮多核作用,但是引入了cache coherency和鎖的問題,比如,Memcached最常用的stats 命令,實際Memcached所有操作都要對這個全局變量加鎖,進行計數等工作,帶來了性能損耗。

2.內存管理方面

memcached 用 slab allocator 機制來管理內存.

slab allocator 原理: 預告把內存劃分成數個 slab class 倉庫

各倉庫,切分成不同尺寸的小塊(chunk). 

需要存內容時,判斷內容的大小,爲其選取合理的倉庫

 

Memcached使用預分配的內存池的方式,使用slab和大小不同的chunk來管理內存,Item根據大小選擇合適的chunk存儲,內存池的 方式可以省去申請/釋放內存的開銷,並且能減小內存碎片產生,但這種方式也會帶來一定程度上的空間浪費,並且在內存仍然有很大空間時,新的數據也可能會被 剔除,原因可以參考Timyang的文章:http://timyang.NET/data/Memcached-lru-evictions/

memcache可能會因爲內存不夠用而刪除一些數據(lru算法

 

如果有 100byte 的內容要存,但 122 大小的倉庫中的 chunk 滿了

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

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

惰性刪除機制

 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

原理: 當某個單元被請求時,維護一個計數器,通過計數器來判斷最近誰最少被使用.

 即使某個 key 是設置的永久有效期,也一樣會被踢出來! 即--永久數據被踢現象

  Redis使用現場申請內存的方式來存儲數據,並且很少使用free-list等方式來優化內存分配,會在一定程度上存在內存碎片,Redis跟據存 儲命令參數,會把帶過期時間的數據單獨存放在一起,並把它們稱爲臨時數據,非臨時數據是永遠不會被剔除的,即便物理內存不夠,導致swap也不會剔除任何 非臨時數據(但會嘗試剔除部分臨時數據),這點上Redis更適合作爲存儲而不是cache。

3.數據一致性問題

Memcached提供了cas命令,可以保證多個併發訪問操作同一份數據的一致性問題。 Redis沒有提供cas 命令,並不能保證這點,不過Redis提供了事務的功能,可以保證一串 命令的原子性,中間不會被任何操作打斷。

cas命令說明:

CAS(check and save)模式

     1.預先在memcached中設置一個key值,假設爲check=1

     2.每次創建活動時,在規則校驗前先get出check=x;

     3.進行規則校驗

     4.執行incr check操作,檢驗返回值是否爲所期望的x+1,如果不是,則說明在此期間有另外的進程執行了incr操作,即存在併發,放棄更新。否則

     5.執行創建活動

memcached保存的key value都有一個唯一標識casUnique,在進行incr decr操作時,首先獲取casUnique,執行incr,檢驗返回值是否casUnique+1,如果是,則更新,否則,失敗不更新!

memcached 1.2.5以及更高版本,提供了gets和cas命令,如果您使用gets命令查詢某個key的item,memcached會 給您返回該item當前值的唯一標識。如果您覆寫了這個item並想把它寫回到memcached中,您可以通過cas命令把那個唯一標識一起發送給 memcached。如果該item存放在memcached中的唯一標識與您提供的一致,您的寫操作將會成功。如果另一個進程在這期間也修改了這個 item,那麼該item存放在memcached中的唯一標識將會改變,寫操作就會失敗。

memcahe的原子性操作有 add incre  decre cas  適合做一些併發鎖

incr,decr 操作是把值理解爲 32 位無符號來+-操作的. 值在[0-2^32-1]範圍內

4.存儲方式及其它方面

flag 的意義:

memcached 基本文本協議,傳輸的東西,理解成字符串來存儲.

想:讓你存一個 PHP 對象,和一個 php 數組

序列化成字符串,往出取的時候,自然還要反序列化成 對象/數組/json 格式等等.

這時候, flag 的意義就體現出來了.

flag 使用MEMCACHE_COMPRESSED標記對數據進行壓縮(使用zlib)

比如, 1 就是字符串, 2 反轉成數組 3,反序列化對象.....

key的限制 250個字符

https://github.com/memcached/memcached/blob/master/memcached.h

value的限制 1M

https://github.com/memcached/memcached/blob/master/memcached.c

memcache 命令

Set:添加一個新條目到memcached或是用新的數據替換替換掉已存在的條目 
Add:當KEY不存在的情況下,它向memcached存數據,否則,返回NOT_STORED響應 
Replace:當KEY存在的情況下,它纔會向memcached存數據,否則返回NOT_STORED響應 
Cas:改變一個存在的KEY值 ,但它還帶了檢查的功能 
Append:在這個值後面插入新值 
Prepend:在這個值前面插入新值 
 

Get:取單個值 ,從緩存中返回數據時,將在第一行得到KEY的名字,flag的值和返回的value長度,真正的數據在第二行,最後返回END,如KEY不存在,第一行就直接返回END 
Get_multi:一次性取多個值 

分佈式
各個memcached服務器之間互不通信,各自獨立存取數據,不共享任何信息。服務器並不具有分佈式功能,分佈式部署取決於memcache客戶端。

分佈式算法(Consistent Hashing): 

選擇服務器算法有兩種,一種是根據餘數來計算分佈,另一種是根據散列算法來計算分佈。 
餘數算法: 
先求得鍵的整數散列值,再除以服務器臺數,根據餘數確定存取服務器,這種方法計算簡單,高效,但在memcached服務器增加或減少時,幾乎所有的緩存都會失效。 

 

散列算法: 

在php.ini中,如下配置:memcache.hash_strategy = consistent
這樣: nginx與PHP即可完成對memcached的集羣與負載均衡算法.

 

一致性哈希的算法把取餘算法的等於號來選擇mem服務器變成了大於號來選擇mem服務器,這應該是纔是關鍵,可以使一個鍵的mem服務器落點變成是動態選擇(一個服務器down掉然後選擇crc32()後大於這個服務器的落點....)

添加虛擬節點,虛擬節點其實還是原來那幾臺服務器,每個虛擬節點都對應一個真實的服務器,起到分散節點的作用

 

A1,A2的實際服務器就是A服務器.....

<?php

class ConsistentHash{

protected $nodes= array();

protected $postion= array();

protected $mul= 32; //每個節點對應 32 個虛節點

public function hash($str) {

    return sprintf('%u',crc32($str)); // 把字符串轉成 32 位符號整數

}

//查找key落到那個節點上

 

public function findNode($key) {

 

    $point= $this->hash($key);

    $node= current($this->postion); //先取圓環上最小的一個節點

    foreach($this->postionas $k=>$v) {

        if($point<= $k) {

            $node= $v;

            break;

        }

    }

    reset($this->postion);//復位數組指針

    return $node;//$key哈希後比最大的節點都大 就放到第一個節點

}

public function addNode($node) {

    if(isset($this->nodes[$node])) {

        return;

    }

    for($i=0; $i<$this->mul; $i++) {

        $pos= $this->hash($node. '-' . $i);//$node = '168.10.1.72:8888'

        $this->postion[$pos] = $node;

        $this->nodes[$node][] = $pos;//方便刪除對應的虛擬節點

    }

    $this->sortPos();

}

 

public function delNode($node) {

    if(!isset($this->nodes[$node])) {

         return;

    }

    foreach($this->nodes[$node] as $k) {

        unset($this->postion[$k]);//刪除對應的虛節點

    }

        unset($this->nodes[$node]);

}

protected function sortPos() {

    ksort($this->postion,SORT_REGULAR);//SORT_REGULAR - 正常比較單元(不改變類型)

}

}

// 使用測試-----start

 

$con = new ConsistentHash();

 

//比如配置文件 $memServerConfArr = ['168.10.1.7:5566','168.10.1.2:7788','168.10.1.72:8899']

 

$memServerConfArr= array('168.10.1.7:5566','168.10.1.2:7788','168.10.1.72:8899');

 

foreach ($memServerConfArras $mem_config) {

    $con->addNode($mem_config);//添加節點

}

$key = 'www.lashou.com';

$memNode= $con->findNode($key);

//echo($memNode);die();  測試落到 168.10.1.7:5566這個節點上

 

$mem = explode(':', $memNode);

 

$host = $mem[0];

$port = $mem[1];

$memcache= new Memcache();

$memcache->connect($host, $port);

$memcache->set($key, 'test_string', MEMCACHE_COMPRESSED, 50);

//------------end

 

//取的時候是一樣的start-------->>>end

 

先 算出memcached服務器的散列值,並將其分佈到0到2的32次方的圓上,然後用同樣的方法算出存儲數據的鍵的散列值並映射至圓上,最後從數據映射到 的位置開始順時針查找,將數據保存到查找到的第一個服務器上,如果超過2的32次方,依然找不到服務器,就將數據保存到第一臺memcached服務器 上。如果添加了一臺memcached服務器,只在圓上增加服務器的逆時針方向的第一臺服務器上的鍵會受到影響。

把服務節點用crc32()函數轉換成32位整數 sprintf('%u',crc32($str)) 無符號32位整數

 cache的hash計算,一般的方法可以使用cache機器的IP地址或者機器名作爲hash輸入。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章