Memcached 編譯安裝部署、LRU 演算法、分散式演算法剖析

Packie發表於2019-06-19

每次面試都問 memcached ,我說”沒用過,但是瞭解,我一般使用 redis 多一點”。
面試官就瞧不起人了,然後今天看了看了書籍,閱讀了下原始碼,除了一些巧妙的做法,也沒有什麼太多說不理解的難點,可能我看得還不夠深入,但是這些巧妙的做法值得學習的。

1. 編譯安裝 Memcached 服務端

libevent 官方下載渠道
Memcache 官方下載渠道

[vagrant@centos7 ~]$ wget https://github.com/libevent/libevent/releases/download/release-2.1.10-stable/libevent-2.1.10-stable.tar.gz

[vagrant@centos7 ~]$ tar -zxvf libevent-2.1.10-stable.tar.gz
[vagrant@centos7 ~]$ cd libevent-2.1.10-stable
[vagrant@centos7 libevent-2.1.10-stable]$ ./configure --prefix=/usr/local
[vagrant@centos7 libevent-2.1.10-stable]$ make
[vagrant@centos7 libevent-2.1.10-stable]$ sudo make install

[vagrant@centos7 ~]$ wget https://memcached.org/files/memcached-1.5.16.tar.gz
[vagrant@centos7 ~]$ tar -zxvf memcached-1.5.16.tar.gz
[vagrant@centos7 ~]$ cd memcached-1.5.16/
[vagrant@centos7 memcached-1.5.16]$ ./configure --prefix=/usr/local/memcached
[vagrant@centos7 memcached-1.5.16]$ make
[vagrant@centos7 memcached-1.5.16]$ sudo make install
[vagrant@centos7 memcached-1.5.16]$ ln -s /usr/local/lib/libevent-2.1.so.6 /usr/lib64/libevent-2.1.so.6

[vagrant@centos7 bin]$ /usr/local/memcached/bin/memcached -d -m 128 -u root -p 11211

2. PHP 安裝擴充 memcache

詳細教程可看 https://www.php.net/manual/en/memcache.ins...
可以使用 pecl 安裝或者編譯安裝,pecl 的源只支援 php5
Github-PHP7-memcache

[vagrant@lnmp-new ~]$ git clone https://github.com/websupport-sk/pecl-memcache
[vagrant@lnmp-new ~]$ cd pecl-memcache/
[vagrant@lnmp-new pecl-memcache]$ /usr/local/php7/bin/phpize
[vagrant@lnmp-new pecl-memcache]$ ./configure --enable-memcache --with-php-config=/usr/local/php7/bin/php-config
[vagrant@lnmp-new pecl-memcache]$ sudo make && make install

[vagrant@lnmp-new pecl-memcache] sudo cp memcache.so /usr/local/php7/lib/php/extensions/no-debug-non-zts-20180731/

[vagrant@lnmp-new pecl-memcache] vi /usr/local/php7/lib/php.ini

#增加以下兩句
extension_dir = "/usr/local/php7/lib/php/extensions/no-debug-non-zts-20180731"
extension = "memcache.so"

#檢視是否啟用
[vagrant@lnmp-new] systemctl php-fpm restart
[vagrant@lnmp-new] php -m | grep memcache 

3. memcached 如何支援高併發

memcache 支援多路複用 I/O 模型(如 epoll,select 等)。傳統阻塞 I/O 中,系統可能會因為某個使用者連線還沒做好 I/O 準備而一直等待,知道這個連線做好 I/O 準備,如果這時有其他使用者連線到伺服器,很可能會因為系統阻塞而得不到響應。

而多路複用 I/O 是一種訊息通知模式,使用者連線做好 I/O 準備後,系統會通知我們這個連線可以進行,這樣就不會阻塞在某個使用者連線。因此,Memcached 才能支援高併發。

4. memcached 使用 LRU 演算法淘汰資料

LRU (Least Recently Used)

it = slabs_alloc(ntotal,id);

// 申請記憶體失敗,就開始淘汰資料
if (it == 0){
    int tries = 50;
    ...
    //從尾部開始遍歷
    for (search = tails[id];tries > 0 && search != NULL;tries--,search = search->prev){
        // 如果引用次數為 0,就釋放掉
        if(search ->refcount == 0){
            ...
            do_item_unlink(search);
            break;
        }
    }

    // 再次申請記憶體,可能都有引用了
    it = slabs_alloc(ntotal,id);
    if (it == 0){
        tries = 50;
        for(search = tails[id];tries > 0 && search != NULL; tries--,search = search ->prev){
        //查詢三小時沒有訪問過的資料,然後將它引用設定為零,再釋放掉
            if (search ->refcont != 0 && search->time + 10800 < current_time){
                search->refcount =0;
                do_item_unlink(search);
                break;
            }
        }

        //如果還是申請不到記憶體,那麼久返回 NULL
        it = slabs_alloc(ntotal,id);
        if(it == 0){
            return NULL;
        }
    }
}

5. memcached 多執行緒模型

主執行緒:接受客戶端連線,並把連線分配給工作執行緒處理
工作執行緒:處理客戶端連線的請求

多執行緒模型

主執行緒監聽客戶端,客戶端連線 memcached 時,memcached呼叫 accept 函式接收到來的連線,然後 push 到工作執行緒的 CQ 佇列,並給工作執行緒發一個訊號,通知工作執行緒有新的客戶端連線需要處理

當工作執行緒收到主執行緒的訊號,便會把 CQ 佇列的客戶端連結註冊到 libevent 進行監聽,libevent 就會偵聽客戶端連線的讀寫事件,並呼叫相關的回撥函式。

6. memcached 分散式佈置方案

如果要佈置多臺 memcached 伺服器,怎樣確定一個資料應該儲存到哪臺伺服器上面?
有兩種方案:第一種是普通的 Hash 分佈,第二種是一致性的 Hash 分佈。

a) 普通的 Hash 分佈

function mHash($key) {
    $md5 = substr(md5($key),0,8);
    $seed = 31;
    $hash = 0;

    for ($i=0;$i<8;$i++){
        $hash = $hash * $seed + ord($md5($i));
        $i ++;
    }

    return $hash ;
}

$sercers = array(
    array(
        "host"=>'192.168.1.12',"port"=>6397
    )
    array(
        "host"=>'192.168.1.14',"port"=>6397
    )
);

$key = "name";
$value = 'biyongyao';

$sc = $server[mHash($key)%2];
$memcached = new Memcached($sc);
$memcached->set($key,$value);

如果伺服器不發生改變,這種方法很好地執行
如果增加一臺伺服器,那麼 key 經過 hash 之後和之前的伺服器對不上了,這就導致了市局丟失,那麼就需要使用一致性 Hash 分佈

b)一致性 Hash 分佈

$key1 = mHash('key1');
$key2 = mHash('key2');
$key3 = mHash('key3');
$key4 = mHash('key4');

$server1 = mHash('192.168.1.1');
$server2 = mHash('192.168.1.2');
$server3 = mHash('192.168.1.3');

將 32 位的整數(0 到 2^31 -1)想象成一個圓環,0 是開頭,2^31 -1 是尾


把四個 key 經過 mhash 整成整數,然後在圓環給它一個位置


把三臺伺服器放到環中


以逆時針開始劃分,把相應的 key 儲存到伺服器上
key4,key3 屬於 server2,key2 屬於 server1,key1 屬於 server3


如果 server2 發生故障,那麼開始屬於 server2 的 key3 和 key4 就歸屬 server1 管


如果新增一臺伺服器放在 key3 和 key4 之間,那麼原來屬於 server2 的 key4 就歸屬 server4 管


c) 一致性 Hash 分佈演算法的例項

/**
 * memcache 分散式演算法,一致性 hash
 * Class FlexiHash
 */
class FlexiHash
{
    use hashTools;

    private $serverList = array();

    private $isSorted = false;

    public function addServer($server)
    {
        $hash = $this->mHash1($server);

        if ( !isset($this->serverList[$hash])) {
            $this->serverList[$hash] = $server;
        }

        $this->isSorted = false;

        return true;
    }

    public function removeServer($service)
    {
        $hash = $this->mHash1($service);

        if (isset($this->serverList[$hash])) {
            unset($this->serverList[$hash]);
        }

        $this->isSorted = false;

        return true;
    }

    public function lookup($key)
    {
        $hash = $this->mHash1($key);

        if ( !$this->isSorted) {
            krsort($this->serverList, SORT_NUMERIC);
            $this->isSorted = true;
        }

        foreach ($this->serverList as $pos => $server) {
            if ($hash >= $pos) {
                return $server;
            }
        }

        return end($this->serverList);
    }
}

d) 測試一致性演算法

$hashServer = new FlexiHash();

$hashServer->addServer('192.168.1.1');
$hashServer->addServer('192.168.1.2');
$hashServer->addServer('192.168.1.3');
$hashServer->addServer('192.168.1.4');

echo 'save key1 in server:',$hashServer->lookup('key1').PHP_EOL;
echo 'save key2 in server:',$hashServer->lookup('key2').PHP_EOL;

echo '####################'.PHP_EOL;

$hashServer->removeServer('192.168.1.2');
echo 'save key3 in server:',$hashServer->lookup('key3').PHP_EOL;
echo 'save key4 in server:',$hashServer->lookup('key4').PHP_EOL;

echo '####################'.PHP_EOL;

$hashServer->addServer('192.168.1.6');
echo 'save name in server:',$hashServer->lookup('name').PHP_EOL;
echo 'save girl in server:',$hashServer->lookup('girl').PHP_EOL;

結果可看,一致性 hash 演算法只會改變很少一部分的資料,從而減少了資料丟失的情況

剛進入這個社群,只發布了少量文章,更多請關注我的 blog :https://blog.biyongyao.com

本作品採用《CC 協議》,轉載必須註明作者和本文連結

Bilyooyam

相關文章