1. 程式人生 > >memcached的學習(7)

memcached的學習(7)

2018.6.13

memcached客戶端實現分散式的相關知識

memcached的分散式

正如第1次中介紹的那樣, memcached雖然稱為“分散式”快取伺服器,但伺服器端並沒有“分散式”功能。 伺服器端僅包括 第2次、 第3次 前阪介紹的記憶體儲存功能,其實現非常簡單。 至於memcached的分散式,則是完全由客戶端程式庫實現的。 這種分散式是memcached的最大特點。

memcached的分散式是什麼意思?

這裡多次使用了“分散式”這個詞,但並未做詳細解釋。 現在開始簡單地介紹一下其原理,各個客戶端的實現基本相同。

下面假設memcached伺服器有node1~node3三臺, 應用程式要儲存鍵名為“tokyo”“kanagawa”“chiba”“saitama”“gunma” 的資料。
在這裡插入圖片描述


首先向memcached中新增“tokyo”。將“tokyo”傳給客戶端程式庫後, 客戶端實現的演算法就會根據“鍵”來決定儲存資料的memcached伺服器。 伺服器選定後,即命令它儲存“tokyo”及其值。
在這裡插入圖片描述
同樣,“kanagawa”“chiba”“saitama”“gunma”都是先選擇伺服器再儲存。
接下來獲取儲存的資料。獲取時也要將要獲取的鍵“tokyo”傳遞給函式庫。 函式庫通過與資料儲存時相同的演算法,根據“鍵”選擇伺服器。 使用的演算法相同,就能選中與儲存時相同的伺服器,然後傳送get命令。 只要資料沒有因為某些原因被刪除,就能獲得儲存的值。
在這裡插入圖片描述
這樣,將不同的鍵儲存到不同的伺服器上,就實現了memcached的分散式。 memcached伺服器增多後,鍵就會分散,即使一臺memcached伺服器發生故障 無法連線,也不會影響其他的快取,系統依然能繼續執行。

接下來介紹第1次 中提到的Perl客戶端函式庫Cache::Memcached實現的分散式方法。

Cache::Memcached的分散式方法

Perl的memcached客戶端函式庫Cache::Memcached是 memcached的作者Brad Fitzpatrick的作品,可以說是原裝的函式庫了。

  ● Cache::Memcached - search.cpan.org
該函式庫實現了分散式功能,是memcached標準的分散式方法。
根據餘數計算分散

Cache::Memcached的分散式方法簡單來說,就是“根據伺服器臺數的餘數進行分散”。 求得鍵的整數雜湊值,再除以伺服器臺數,根據其餘數來選擇伺服器。

下面將Cache::Memcached簡化成以下的Perl指令碼來進行說明。

use strict;
use warnings;
use String::CRC32;

my @nodes = ('node1','node2','node3');
my @keys = ('tokyo', 'kanagawa', 'chiba', 'saitama', 'gunma');

foreach my $key (@keys) {
    my $crc = crc32($key);             # CRC値
    my $mod = $crc % ( $#nodes + 1 );
    my $server = $nodes[ $mod ];       # 根據餘數選擇伺服器
    printf "%s => %s\n", $key, $server;
}
Cache::Memcached在求雜湊值時使用了CRC。
 ● String::CRC32 - search.cpan.org

首先求得字串的CRC值,根據該值除以伺服器節點數目得到的餘數決定伺服器。 上面的程式碼執行後輸入以下結果:

tokyo       => node2
kanagawa => node3
chiba       => node2
saitama   => node1
gunma     => node1

根據該結果,“tokyo”分散到node2,“kanagawa”分散到node3等。 多說一句,當選擇的伺服器無法連線時,Cache::Memcached會將連線次數 新增到鍵之後,再次計算雜湊值並嘗試連線。這個動作稱為rehash。 不希望rehash時可以在生成Cache::Memcached物件時指定“rehash => 0”選項。

根據餘數計算分散的缺點

餘數計算的方法簡單,資料的分散性也相當優秀,但也有其缺點。 那就是當新增或移除伺服器時,快取重組的代價相當巨大。 新增伺服器後,餘數就會產生鉅變,這樣就無法獲取與儲存時相同的伺服器, 從而影響快取的命中率。用Perl寫段程式碼來驗證其代價。

use strict;
use warnings;
use String::CRC32;

my @nodes = @ARGV;
my @keys = ('a'..'z');
my %nodes;

foreach my $key ( @keys ) {
    my $hash = crc32($key);
    my $mod = $hash % ( $#nodes + 1 );
    my $server = $nodes[ $mod ];
    push @{ $nodes{ $server } }, $key;
}

foreach my $node ( sort keys %nodes ) {
    printf "%s: %s\n", $node,  join ",", @{ $nodes{$node} };
}

這段Perl指令碼演示了將“a”到“z”的鍵儲存到memcached並訪問的情況。 將其儲存為mod.pl並執行。

首先,當伺服器只有三臺時:

$ mod.pl node1 node2 nod3
node1: a,c,d,e,h,j,n,u,w,x
node2: g,i,k,l,p,r,s,y
node3: b,f,m,o,q,t,v,z

結果如上,node1儲存a、c、d、e……,node2儲存g、i、k……, 每臺伺服器都儲存了8個到10個數據。
接下來增加一臺memcached伺服器。

$ mod.pl node1 node2 node3 node4
node1: d,f,m,o,t,v
node2: b,i,k,p,r,y
node3: e,g,l,n,u,w
node4: a,c,h,j,q,s,x,z

添加了node4。可見,只有d、i、k、p、r、y命中了。像這樣,新增節點後 鍵分散到的伺服器會發生巨大變化。26個鍵中只有六個在訪問原來的伺服器, 其他的全都移到了其他伺服器。命中率降低到23%。在Web應用程式中使用memcached時, 在新增memcached伺服器的瞬間快取效率會大幅度下降,負載會集中到資料庫伺服器上, 有可能會發生無法提供正常服務的情況。

mixi的Web應用程式運用中也有這個問題,導致無法新增memcached伺服器。 但由於使用了新的分散式方法,現在可以輕而易舉地新增memcached伺服器了。 這種分散式方法稱為 Consistent Hashing。

Consistent Hashing

Consistent Hashing的簡單說明

Consistent Hashing如下所示:首先求出memcached伺服器(節點)的雜湊值, 並將其配置到0~232的圓(continuum)上。 然後用同樣的方法求出儲存資料的鍵的雜湊值,並對映到圓上。 然後從資料對映到的位置開始順時針查詢,將資料儲存到找到的第一個伺服器上。 如果超過232仍然找不到伺服器,就會儲存到第一臺memcached伺服器上。

從上圖的狀態中新增一臺memcached伺服器。餘數分散式演算法由於儲存鍵的伺服器會發生巨大變化 而影響快取的命中率,但Consistent Hashing中,只有在continuum上增加伺服器的地點逆時針方向的 第一臺伺服器上的鍵會受到影響。

在這裡插入圖片描述
因此,Consistent Hashing最大限度地抑制了鍵的重新分佈。 而且,有的Consistent Hashing的實現方法還採用了虛擬節點的思想。 使用一般的hash函式的話,伺服器的對映地點的分佈非常不均勻。 因此,使用虛擬節點的思想,為每個物理節點(伺服器) 在continuum上分配100~200個點。這樣就能抑制分佈不均勻, 最大限度地減小伺服器增減時的快取重新分佈。

通過下文中介紹的使用Consistent Hashing演算法的memcached客戶端函式庫進行測試的結果是, 由伺服器臺數(n)和增加的伺服器臺數(m)計算增加伺服器後的命中率計算公式如下:

(1 - n/(n+m)) * 100
支援Consistent Hashing的函式庫

本連載中多次介紹的Cache::Memcached雖然不支援Consistent Hashing, 但已有幾個客戶端函式庫支援了這種新的分散式演算法。 第一個支援Consistent Hashing和虛擬節點的memcached客戶端函式庫是 名為libketama的PHP庫,由last.fm開發。

● libketama - a consistent hashing algo for memcache clients - Users at Last.fm
至於Perl客戶端,連載的第1次 中介紹過的Cache::Memcached::Fast和Cache::Memcached::libmemcached支援 Consistent Hashing。

● Cache::Memcached::Fast - search.cpan.org

● Cache::Memcached::libmemcached - search.cpan.org

兩者的介面都與Cache::Memcached幾乎相同,如果正在使用Cache::Memcached, 那麼就可以方便地替換過來。Cache::Memcached::Fast重新實現了libketama, 使用Consistent Hashing建立物件時可以指定ketama_points選項。
my $memcached = Cache::Memcached::Fast->new({
servers => [“192.168.0.1:11211”,“192.168.0.2:11211”],
ketama_points => 150
}

另外,Cache::Memcached::libmemcached 是一個使用了Brain Aker開發的C函式庫libmemcached的Perl模組。 libmemcached本身支援幾種分散式演算法,也支援Consistent Hashing, 其Perl繫結也支援Consistent Hashing。

● Tangent Software: libmemcached

總結

本次介紹了memcached的分散式演算法,主要有memcached的分散式是由客戶端函式庫實現, 以及高效率地分散資料的Consistent Hashing演算法。