1. memcached的基礎(chǔ)
翻譯一篇技術(shù)評論社的文章,是講memcached的連載。fcicq同學說這個東西很有用,希望大家喜歡。
發(fā)表日:2008/7/2
作者:長野雅廣(Masahiro Nagano)
原文鏈接:http://gihyo.jp/dev/feature/01/memcached/0001
我是mixi株式會社開發(fā)部系統(tǒng)運營組的長野。 日常負責程序的運營。從今天開始,將分幾次針對最近在Web應(yīng)用的可擴展性領(lǐng)域 的熱門話題memcached,與我公司開發(fā)部研究開發(fā)組的前坂一起, 說明其內(nèi)部結(jié)構(gòu)和使用。
memcached是什么?
memcached 是以LiveJournal 旗下Danga Interactive 公司的Brad Fitzpatric 為首開發(fā)的一款軟件。現(xiàn)在已成為 mixi、 hatena、 Facebook、 Vox、LiveJournal等眾多服務(wù)中 提高Web應(yīng)用擴展性的重要因素。
許多Web應(yīng)用都將數(shù)據(jù)保存到RDBMS中,應(yīng)用服務(wù)器從中讀取數(shù)據(jù)并在瀏覽器中顯示。 但隨著數(shù)據(jù)量的增大、訪問的集中,就會出現(xiàn)RDBMS的負擔加重、數(shù)據(jù)庫響應(yīng)惡化、 網(wǎng)站顯示延遲等重大影響。
這時就該memcached大顯身手了。memcached是高性能的分布式內(nèi)存緩存服務(wù)器。 一般的使用目的是,通過緩存數(shù)據(jù)庫查詢結(jié)果,減少數(shù)據(jù)庫訪問次數(shù),以提高動態(tài)Web應(yīng)用的速度、 提高可擴展性。

圖1 一般情況下memcached的用途
memcached的特征
memcached作為高速運行的分布式緩存服務(wù)器,具有以下的特點。
- 協(xié)議簡單
- 基于libevent的事件處理
- 內(nèi)置內(nèi)存存儲方式
- memcached不互相通信的分布式
協(xié)議簡單
memcached的服務(wù)器客戶端通信并不使用復(fù)雜的XML等格式, 而使用簡單的基于文本行的協(xié)議。因此,通過telnet 也能在memcached上保存數(shù)據(jù)、取得數(shù)據(jù)。下面是例子。
$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
set foo 0 0 3 (保存命令)
bar (數(shù)據(jù))
STORED (結(jié)果)
get foo (取得命令)
VALUE foo 0 3 (數(shù)據(jù))
bar (數(shù)據(jù))
協(xié)議文檔位于memcached的源代碼內(nèi),也可以參考以下的URL。
基于libevent的事件處理
libevent是個程序庫,它將Linux的epoll、BSD類操作系統(tǒng)的kqueue等事件處理功能 封裝成統(tǒng)一的接口。即使對服務(wù)器的連接數(shù)增加,也能發(fā)揮O(1)的性能。 memcached使用這個libevent庫,因此能在Linux、BSD、Solaris等操作系統(tǒng)上發(fā)揮其高性能。 關(guān)于事件處理這里就不再詳細介紹,可以參考Dan Kegel的The C10K Problem。
- libevent: http://www.monkey.org/~provos/libevent/
- The C10K Problem: http://www.kegel.com/c10k.html
內(nèi)置內(nèi)存存儲方式
為了提高性能,memcached中保存的數(shù)據(jù)都存儲在memcached內(nèi)置的內(nèi)存存儲空間中。 由于數(shù)據(jù)僅存在于內(nèi)存中,因此重啟memcached、重啟操作系統(tǒng)會導致全部數(shù)據(jù)消失。 另外,內(nèi)容容量達到指定值之后,就基于LRU(Least Recently Used)算法自動刪除不使用的緩存。 memcached本身是為緩存而設(shè)計的服務(wù)器,因此并沒有過多考慮數(shù)據(jù)的永久性問題。 關(guān)于內(nèi)存存儲的詳細信息,本連載的第二講以后前坂會進行介紹,請屆時參考。
memcached不互相通信的分布式
memcached盡管是“分布式”緩存服務(wù)器,但服務(wù)器端并沒有分布式功能。 各個memcached不會互相通信以共享信息。那么,怎樣進行分布式呢? 這完全取決于客戶端的實現(xiàn)。本連載也將介紹memcached的分布式。

圖2 memcached的分布式
接下來簡單介紹一下memcached的使用方法。
安裝memcached
memcached的安裝比較簡單,這里稍加說明。
memcached支持許多平臺。
- Linux
- FreeBSD
- Solaris (memcached 1.2.5以上版本)
- Mac OS X
另外也能安裝在Windows上。這里使用Fedora Core 8進行說明。
memcached的安裝
運行memcached需要本文開頭介紹的libevent庫。Fedora 8中有現(xiàn)成的rpm包, 通過yum命令安裝即可。
$ sudo yum install libevent libevent-devel
memcached的源代碼可以從memcached網(wǎng)站上下載。本文執(zhí)筆時的最新版本為1.2.5。 Fedora 8雖然也包含了memcached的rpm,但版本比較老。因為源代碼安裝并不困難, 這里就不使用rpm了。
- 下載memcached:http://www.danga.com/memcached/download.bml
memcached安裝與一般應(yīng)用程序相同,configure、make、make install就行了。
$ wget http://www.danga.com/memcached/dist/memcached-1.2.5.tar.gz
$ tar zxf memcached-1.2.5.tar.gz
$ cd memcached-1.2.5
$ ./configure
$ make
$ sudo make install
默認情況下memcached安裝到/usr/local/bin下。
memcached的啟動
從終端輸入以下命令,啟動memcached。
$ /usr/local/bin/memcached -p 11211 -m 64m -vv
slab class 1: chunk size 88 perslab 11915
slab class 2: chunk size 112 perslab 9362
slab class 3: chunk size 144 perslab 7281
中間省略
slab class 38: chunk size 391224 perslab 2
slab class 39: chunk size 489032 perslab 2
<23 server listening
<24 send buffer was 110592, now 268435456
<24 server listening (udp)
<24 server listening (udp)
<24 server listening (udp)
<24 server listening (udp)
這里顯示了調(diào)試信息。這樣就在前臺啟動了memcached,監(jiān)聽TCP端口11211 最大內(nèi)存使用量為64M。調(diào)試信息的內(nèi)容大部分是關(guān)于存儲的信息, 下次連載時具體說明。
作為daemon后臺啟動時,只需
$ /usr/local/bin/memcached -p 11211 -m 64m -d
這里使用的memcached啟動選項的內(nèi)容如下。
選項 | 說明 |
-p | 使用的TCP端口。默認為11211 |
-m | 最大內(nèi)存大小。默認為64M |
-vv | 用very vrebose模式啟動,調(diào)試信息和錯誤輸出到控制臺 |
-d | 作為daemon在后臺啟動 |
上面四個是常用的啟動選項,其他還有很多,通過
$ /usr/local/bin/memcached -h
命令可以顯示。許多選項可以改變memcached的各種行為, 推薦讀一讀。
用客戶端連接
許多語言都實現(xiàn)了連接memcached的客戶端,其中以Perl、PHP為主。 僅僅memcached網(wǎng)站上列出的語言就有
- Perl
- PHP
- Python
- Ruby
- C#
- C/C++
- Lua
等等。
- memcached客戶端API:http://www.danga.com/memcached/apis.bml
這里介紹通過mixi正在使用的Perl庫鏈接memcached的方法。
使用Cache::Memcached
Perl的memcached客戶端有
- Cache::Memcached
- Cache::Memcached::Fast
- Cache::Memcached::libmemcached
等幾個CPAN模塊。這里介紹的Cache::Memcached是memcached的作者Brad Fitzpatric的作品, 應(yīng)該算是memcached的客戶端中應(yīng)用最為廣泛的模塊了。
- Cache::Memcached - search.cpan.org: http://search.cpan.org/dist/Cache-Memcached/
使用Cache::Memcached連接memcached
下面的源代碼為通過Cache::Memcached連接剛才啟動的memcached的例子。
#!/usr/bin/perl
use strict;
use warnings;
use Cache::Memcached;
my $key = "foo";
my $value = "bar";
my $expires = 3600; # 1 hour
my $memcached = Cache::Memcached->new({
servers => ["127.0.0.1:11211"],
compress_threshold => 10_000
});
$memcached->add($key, $value, $expires);
my $ret = $memcached->get($key);
print "$ret"n";
在這里,為Cache::Memcached指定了memcached服務(wù)器的IP地址和一個選項,以生成實例。 Cache::Memcached常用的選項如下所示。
選項 | 說明 |
servers | 用數(shù)組指定memcached服務(wù)器和端口 |
compress_threshold | 數(shù)據(jù)壓縮時使用的值 |
namespace | 指定添加到鍵的前綴 |
另外,Cache::Memcached通過Storable模塊可以將Perl的復(fù)雜數(shù)據(jù)序列化之后再保存, 因此散列、數(shù)組、對象等都可以直接保存到memcached中。
保存數(shù)據(jù)
向memcached保存數(shù)據(jù)的方法有
- add
- replace
- set
它們的使用方法都相同:
my $add = $memcached->add( '鍵', '值', '期限' );
my $replace = $memcached->replace( '鍵', '值', '期限' );
my $set = $memcached->set( '鍵', '值', '期限' );
向memcached保存數(shù)據(jù)時可以指定期限(秒)。不指定期限時,memcached按照LRU算法保存數(shù)據(jù)。 這三個方法的區(qū)別如下:
選項 | 說明 |
add | 僅當存儲空間中不存在鍵相同的數(shù)據(jù)時才保存 |
replace | 僅當存儲空間中存在鍵相同的數(shù)據(jù)時才保存 |
set | 與add和replace不同,無論何時都保存 |
獲取數(shù)據(jù)
獲取數(shù)據(jù)可以使用get和get_multi方法。
my $val = $memcached->get('鍵');
my $val = $memcached->get_multi('鍵1', '鍵2', '鍵3', '鍵4', '鍵5');
一次取得多條數(shù)據(jù)時使用get_multi。get_multi可以非同步地同時取得多個鍵值, 其速度要比循環(huán)調(diào)用get快數(shù)十倍。
刪除數(shù)據(jù)
刪除數(shù)據(jù)使用delete方法,不過它有個獨特的功能。
$memcached->delete('鍵', '阻塞時間(秒)');
刪除第一個參數(shù)指定的鍵的數(shù)據(jù)。第二個參數(shù)指定一個時間值,可以禁止使用同樣的鍵保存新數(shù)據(jù)。 此功能可以用于防止緩存數(shù)據(jù)的不完整。但是要注意,set函數(shù)忽視該阻塞,照常保存數(shù)據(jù)
增一和減一操作
可以將memcached上特定的鍵值作為計數(shù)器使用。
my $ret = $memcached->incr('鍵');
$memcached->add('鍵', 0) unless defined $ret;
增一和減一是原子操作,但未設(shè)置初始值時,不會自動賦成0。因此, 應(yīng)當進行錯誤檢查,必要時加入初始化操作。而且,服務(wù)器端也不會對 超過2<sup>32</sup>時的行為進行檢查。
總結(jié)
這次簡單介紹了memcached,以及它的安裝方法、Perl客戶端Cache::Memcached的用法。 只要知道,memcached的使用方法十分簡單就足夠了。
下次由前坂來說明memcached的內(nèi)部結(jié)構(gòu)。了解memcached的內(nèi)部構(gòu)造, 就能知道如何使用memcached才能使Web應(yīng)用的速度更上一層樓。 歡迎繼續(xù)閱讀下一章。
2.理解memcached的內(nèi)存存儲
下面是《memcached全面剖析》的第二部分。
發(fā)表日:2008/7/9
作者:前坂徹(Toru Maesaka)
原文鏈接:http://gihyo.jp/dev/feature/01/memcached/0002
我是mixi株式會社研究開發(fā)組的前坂徹。 上次的文章介紹了memcached是分布式的高速緩存服務(wù)器。 本次將介紹memcached的內(nèi)部構(gòu)造的實現(xiàn)方式,以及內(nèi)存的管理方式。 另外,memcached的內(nèi)部構(gòu)造導致的弱點也將加以說明。
Slab Allocation機制:整理內(nèi)存以便重復(fù)使用
最近的memcached默認情況下采用了名為Slab Allocator的機制分配、管理內(nèi)存。 在該機制出現(xiàn)以前,內(nèi)存的分配是通過對所有記錄簡單地進行malloc和free來進行的。 但是,這種方式會導致內(nèi)存碎片,加重操作系統(tǒng)內(nèi)存管理器的負擔,最壞的情況下, 會導致操作系統(tǒng)比memcached進程本身還慢。Slab Allocator就是為解決該問題而誕生的。
下面來看看Slab Allocator的原理。下面是memcached文檔中的slab allocator的目標:
the primary goal of the slabs subsystem in memcached was to eliminate memory fragmentation issues totally by using fixed-size memory chunks coming from a few predetermined size classes.
也就是說,Slab Allocator的基本原理是按照預(yù)先規(guī)定的大小,將分配的內(nèi)存分割成特定長度的塊, 以完全解決內(nèi)存碎片問題。
Slab Allocation的原理相當簡單。 將分配的內(nèi)存分割成各種尺寸的塊(chunk), 并把尺寸相同的塊分成組(chunk的集合)(圖1)。

圖1 Slab Allocation的構(gòu)造圖
而且,slab allocator還有重復(fù)使用已分配的內(nèi)存的目的。 也就是說,分配到的內(nèi)存不會釋放,而是重復(fù)利用。
Slab Allocation的主要術(shù)語
Page
分配給Slab的內(nèi)存空間,默認是1MB。分配給Slab之后根據(jù)slab的大小切分成chunk。
Chunk
用于緩存記錄的內(nèi)存空間。
Slab Class
特定大小的chunk的組。
在Slab中緩存記錄的原理
下面說明memcached如何針對客戶端發(fā)送的數(shù)據(jù)選擇slab并緩存到chunk中。
memcached根據(jù)收到的數(shù)據(jù)的大小,選擇最適合數(shù)據(jù)大小的slab(圖2)。 memcached中保存著slab內(nèi)空閑chunk的列表,根據(jù)該列表選擇chunk, 然后將數(shù)據(jù)緩存于其中。

圖2 選擇存儲記錄的組的方法
實際上,Slab Allocator也是有利也有弊。下面介紹一下它的缺點。
Slab Allocator的缺點
Slab Allocator解決了當初的內(nèi)存碎片問題,但新的機制也給memcached帶來了新的問題。
這個問題就是,由于分配的是特定長度的內(nèi)存,因此無法有效利用分配的內(nèi)存。 例如,將100字節(jié)的數(shù)據(jù)緩存到128字節(jié)的chunk中,剩余的28字節(jié)就浪費了(圖3)。

圖3 chunk空間的使用
對于該問題目前還沒有完美的解決方案,但在文檔中記載了比較有效的解決方案。
The most efficient way to reduce the waste is to use a list of size classes that closely matches (if that's at all possible) common sizes of objects that the clients of this particular installation of memcached are likely to store.
就是說,如果預(yù)先知道客戶端發(fā)送的數(shù)據(jù)的公用大小,或者僅緩存大小相同的數(shù)據(jù)的情況下, 只要使用適合數(shù)據(jù)大小的組的列表,就可以減少浪費。
但是很遺憾,現(xiàn)在還不能進行任何調(diào)優(yōu),只能期待以后的版本了。 但是,我們可以調(diào)節(jié)slab class的大小的差別。 接下來說明growth factor選項。
使用Growth Factor進行調(diào)優(yōu)
memcached在啟動時指定 Growth Factor因子(通過-f選項), 就可以在某種程度上控制slab之間的差異。默認值為1.25。 但是,在該選項出現(xiàn)之前,這個因子曾經(jīng)固定為2,稱為“powers of 2”策略。
讓我們用以前的設(shè)置,以verbose模式啟動memcached試試看:
$ memcached -f 2 -vv
下面是啟動后的verbose輸出:
slab class 1: chunk size 128 perslab 8192
slab class 2: chunk size 256 perslab 4096
slab class 3: chunk size 512 perslab 2048
slab class 4: chunk size 1024 perslab 1024
slab class 5: chunk size 2048 perslab 512
slab class 6: chunk size 4096 perslab 256
slab class 7: chunk size 8192 perslab 128
slab class 8: chunk size 16384 perslab 64
slab class 9: chunk size 32768 perslab 32
slab class 10: chunk size 65536 perslab 16
slab class 11: chunk size 131072 perslab 8
slab class 12: chunk size 262144 perslab 4
slab class 13: chunk size 524288 perslab 2
可見,從128字節(jié)的組開始,組的大小依次增大為原來的2倍。 這樣設(shè)置的問題是,slab之間的差別比較大,有些情況下就相當浪費內(nèi)存。 因此,為盡量減少內(nèi)存浪費,兩年前追加了growth factor這個選項。
來看看現(xiàn)在的默認設(shè)置(f=1.25)時的輸出(篇幅所限,這里只寫到第10組):
slab class 1: chunk size 88 perslab 11915
slab class 2: chunk size 112 perslab 9362
slab class 3: chunk size 144 perslab 7281
slab class 4: chunk size 184 perslab 5698
slab class 5: chunk size 232 perslab 4519
slab class 6: chunk size 296 perslab 3542
slab class 7: chunk size 376 perslab 2788
slab class 8: chunk size 472 perslab 2221
slab class 9: chunk size 592 perslab 1771
slab class 10: chunk size 744 perslab 1409
可見,組間差距比因子為2時小得多,更適合緩存幾百字節(jié)的記錄。 從上面的輸出結(jié)果來看,可能會覺得有些計算誤差, 這些誤差是為了保持字節(jié)數(shù)的對齊而故意設(shè)置的。
將memcached引入產(chǎn)品,或是直接使用默認值進行部署時, 最好是重新計算一下數(shù)據(jù)的預(yù)期平均長度,調(diào)整growth factor, 以獲得最恰當?shù)脑O(shè)置。內(nèi)存是珍貴的資源,浪費就太可惜了。
接下來介紹一下如何使用memcached的stats命令查看slabs的利用率等各種各樣的信息。
查看memcached的內(nèi)部狀態(tài)
memcached有個名為stats的命令,使用它可以獲得各種各樣的信息。 執(zhí)行命令的方法很多,用telnet最為簡單:
$ telnet 主機名 端口號
連接到memcached之后,輸入stats再按回車,即可獲得包括資源利用率在內(nèi)的各種信息。 此外,輸入"stats slabs"或"stats items"還可以獲得關(guān)于緩存記錄的信息。 結(jié)束程序請輸入quit。
這些命令的詳細信息可以參考memcached軟件包內(nèi)的protocol.txt文檔。
$ telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
stats
STAT pid 481
STAT uptime 16574
STAT time 1213687612
STAT version 1.2.5
STAT pointer_size 32
STAT rusage_user 0.102297
STAT rusage_system 0.214317
STAT curr_items 0
STAT total_items 0
STAT bytes 0
STAT curr_connections 6
STAT total_connections 8
STAT connection_structures 7
STAT cmd_get 0
STAT cmd_set 0
STAT get_hits 0
STAT get_misses 0
STAT evictions 0
STAT bytes_read 20
STAT bytes_written 465
STAT limit_maxbytes 67108864
STAT threads 4
END
quit
另外,如果安裝了libmemcached這個面向C/C++語言的客戶端庫,就會安裝 memstat 這個命令。 使用方法很簡單,可以用更少的步驟獲得與telnet相同的信息,還能一次性從多臺服務(wù)器獲得信息。
$ memstat --servers=server1,server2,server3,...
libmemcached可以從下面的地址獲得:
查看slabs的使用狀況
使用memcached的創(chuàng)造著Brad寫的名為memcached-tool的Perl腳本,可以方便地獲得slab的使用情況 (它將memcached的返回值整理成容易閱讀的格式)。可以從下面的地址獲得腳本:
使用方法也極其簡單:
$ memcached-tool 主機名:端口 選項
查看slabs使用狀況時無需指定選項,因此用下面的命令即可:
$ memcached-tool 主機名:端口
獲得的信息如下所示:
# Item_Size Max_age 1MB_pages Count Full?
1 104 B 1394292 s 1215 12249628 yes
2 136 B 1456795 s 52 400919 yes
3 176 B 1339587 s 33 196567 yes
4 224 B 1360926 s 109 510221 yes
5 280 B 1570071 s 49 183452 yes
6 352 B 1592051 s 77 229197 yes
7 440 B 1517732 s 66 157183 yes
8 552 B 1460821 s 62 117697 yes
9 696 B 1521917 s 143 215308 yes
10 872 B 1695035 s 205 246162 yes
11 1.1 kB 1681650 s 233 221968 yes
12 1.3 kB 1603363 s 241 183621 yes
13 1.7 kB 1634218 s 94 57197 yes
14 2.1 kB 1695038 s 75 36488 yes
15 2.6 kB 1747075 s 65 25203 yes
16 3.3 kB 1760661 s 78 24167 yes
各列的含義為:
列 | 含義 |
# | slab class編號 |
Item_Size | Chunk大小 |
Max_age | LRU內(nèi)最舊的記錄的生存時間 |
1MB_pages | 分配給Slab的頁數(shù) |
Count | Slab內(nèi)的記錄數(shù) |
Full? | Slab內(nèi)是否含有空閑chunk |
從這個腳本獲得的信息對于調(diào)優(yōu)非常方便,強烈推薦使用。
內(nèi)存存儲的總結(jié)
本次簡單說明了memcached的緩存機制和調(diào)優(yōu)方法。 希望讀者能理解memcached的內(nèi)存管理原理及其優(yōu)缺點。
下次將繼續(xù)說明LRU和Expire等原理,以及memcached的最新發(fā)展方向—— 可擴充體系(pluggable architecher))。
3.memcached的刪除機制和發(fā)展方向
下面是《memcached全面剖析》的第三部分。
發(fā)表日:2008/7/16
作者:前坂徹(Toru Maesaka)
原文鏈接:http://gihyo.jp/dev/feature/01/memcached/0003
memcached是緩存,所以數(shù)據(jù)不會永久保存在服務(wù)器上,這是向系統(tǒng)中引入memcached的前提。 本次介紹memcached的數(shù)據(jù)刪除機制,以及memcached的最新發(fā)展方向——二進制協(xié)議(Binary Protocol) 和外部引擎支持。
memcached在數(shù)據(jù)刪除方面有效利用資源
數(shù)據(jù)不會真正從memcached中消失
上次介紹過, memcached不會釋放已分配的內(nèi)存。記錄超時后,客戶端就無法再看見該記錄(invisible,透明), 其存儲空間即可重復(fù)使用。
Lazy Expiration
memcached內(nèi)部不會監(jiān)視記錄是否過期,而是在get時查看記錄的時間戳,檢查記錄是否過期。 這種技術(shù)被稱為lazy(惰性)expiration。因此,memcached不會在過期監(jiān)視上耗費CPU時間。
LRU:從緩存中有效刪除數(shù)據(jù)的原理
memcached會優(yōu)先使用已超時的記錄的空間,但即使如此,也會發(fā)生追加新記錄時空間不足的情況, 此時就要使用名為 Least Recently Used(LRU)機制來分配空間。 顧名思義,這是刪除“最近最少使用”的記錄的機制。 因此,當memcached的內(nèi)存空間不足時(無法從slab class 獲取到新的空間時),就從最近未被使用的記錄中搜索,并將其空間分配給新的記錄。 從緩存的實用角度來看,該模型十分理想。
不過,有些情況下LRU機制反倒會造成麻煩。memcached啟動時通過“-M”參數(shù)可以禁止LRU,如下所示:
$ memcached -M -m 1024
啟動時必須注意的是,小寫的“-m”選項是用來指定最大內(nèi)存大小的。不指定具體數(shù)值則使用默認值64MB。
指定“-M”參數(shù)啟動后,內(nèi)存用盡時memcached會返回錯誤。 話說回來,memcached畢竟不是存儲器,而是緩存,所以推薦使用LRU。
memcached的最新發(fā)展方向
memcached的roadmap上有兩個大的目標。一個是二進制協(xié)議的策劃和實現(xiàn),另一個是外部引擎的加載功能。
關(guān)于二進制協(xié)議
使用二進制協(xié)議的理由是它不需要文本協(xié)議的解析處理,使得原本高速的memcached的性能更上一層樓, 還能減少文本協(xié)議的漏洞。目前已大部分實現(xiàn),開發(fā)用的代碼庫中已包含了該功能。 memcached的下載頁面上有代碼庫的鏈接。
二進制協(xié)議的格式
協(xié)議的包為24字節(jié)的幀,其后面是鍵和無結(jié)構(gòu)數(shù)據(jù)(Unstructured Data)。 實際的格式如下(引自協(xié)議文檔):
Byte/ 0 | 1 | 2 | 3 |
/ | | | |
|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+---------------+---------------+---------------+---------------+
0/ HEADER /
/ /
/ /
/ /
+---------------+---------------+---------------+---------------+
24/ COMMAND-SPECIFIC EXTRAS (as needed) /
+/ (note length in th extras length header field) /
+---------------+---------------+---------------+---------------+
m/ Key (as needed) /
+/ (note length in key length header field) /
+---------------+---------------+---------------+---------------+
n/ Value (as needed) /
+/ (note length is total body length header field, minus /
+/ sum of the extras and key length body fields) /
+---------------+---------------+---------------+---------------+
Total 24 bytes
如上所示,包格式十分簡單。需要注意的是,占據(jù)了16字節(jié)的頭部(HEADER)分為 請求頭(Request Header)和響應(yīng)頭(Response Header)兩種。 頭部中包含了表示包的有效性的Magic字節(jié)、命令種類、鍵長度、值長度等信息,格式如下:
Request Header
Byte/ 0 | 1 | 2 | 3 |
/ | | | |
|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+---------------+---------------+---------------+---------------+
0| Magic | Opcode | Key length |
+---------------+---------------+---------------+---------------+
4| Extras length | Data type | Reserved |
+---------------+---------------+---------------+---------------+
8| Total body length |
+---------------+---------------+---------------+---------------+
12| Opaque |
+---------------+---------------+---------------+---------------+
16| CAS |
| |
+---------------+---------------+---------------+---------------+
Response Header
Byte/ 0 | 1 | 2 | 3 |
/ | | | |
|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+---------------+---------------+---------------+---------------+
0| Magic | Opcode | Key Length |
+---------------+---------------+---------------+---------------+
4| Extras length | Data type | Status |
+---------------+---------------+---------------+---------------+
8| Total body length |
+---------------+---------------+---------------+---------------+
12| Opaque |
+---------------+---------------+---------------+---------------+
16| CAS |
| |
+---------------+---------------+---------------+---------------+
如希望了解各個部分的詳細內(nèi)容,可以checkout出memcached的二進制協(xié)議的代碼樹, 參考其中的docs文件夾中的protocol_binary.txt文檔。
HEADER中引人注目的地方
看到HEADER格式后我的感想是,鍵的上限太大了!現(xiàn)在的memcached規(guī)格中,鍵長度最大為250字節(jié), 但二進制協(xié)議中鍵的大小用2字節(jié)表示。因此,理論上最大可使用65536字節(jié)(2<sup>16</sup>)長的鍵。 盡管250字節(jié)以上的鍵并不會太常用,二進制協(xié)議發(fā)布之后就可以使用巨大的鍵了。
二進制協(xié)議從下一版本1.3系列開始支持。
外部引擎支持
我去年曾經(jīng)試驗性地將memcached的存儲層改造成了可擴展的(pluggable)。
MySQL的Brian Aker看到這個改造之后,就將代碼發(fā)到了memcached的郵件列表。 memcached的開發(fā)者也十分感興趣,就放到了roadmap中。現(xiàn)在由我和 memcached的開發(fā)者Trond Norbye協(xié)同開發(fā)(規(guī)格設(shè)計、實現(xiàn)和測試)。 和國外協(xié)同開發(fā)時時差是個大問題,但抱著相同的愿景, 最后終于可以將可擴展架構(gòu)的原型公布了。 代碼庫可以從memcached的下載頁面 上訪問。
外部引擎支持的必要性
世界上有許多memcached的派生軟件,其理由是希望永久保存數(shù)據(jù)、實現(xiàn)數(shù)據(jù)冗余等, 即使犧牲一些性能也在所不惜。我在開發(fā)memcached之前,在mixi的研發(fā)部也曾經(jīng) 考慮過重新發(fā)明memcached。
外部引擎的加載機制能封裝memcached的網(wǎng)絡(luò)功能、事件處理等復(fù)雜的處理。 因此,現(xiàn)階段通過強制手段或重新設(shè)計等方式使memcached和存儲引擎合作的困難 就會煙消云散,嘗試各種引擎就會變得輕而易舉了。
簡單API設(shè)計的成功的關(guān)鍵
該項目中我們最重視的是API設(shè)計。函數(shù)過多,會使引擎開發(fā)者感到麻煩; 過于復(fù)雜,實現(xiàn)引擎的門檻就會過高。因此,最初版本的接口函數(shù)只有13個。 具體內(nèi)容限于篇幅,這里就省略了,僅說明一下引擎應(yīng)當完成的操作:
- 引擎信息(版本等)
- 引擎初始化
- 引擎關(guān)閉
- 引擎的統(tǒng)計信息
- 在容量方面,測試給定記錄能否保存
- 為item(記錄)結(jié)構(gòu)分配內(nèi)存
- 釋放item(記錄)的內(nèi)存
- 刪除記錄
- 保存記錄
- 回收記錄
- 更新記錄的時間戳
- 數(shù)學運算處理
- 數(shù)據(jù)的flush
對詳細規(guī)格有興趣的讀者,可以checkout engine項目的代碼,閱讀器中的engine.h。
重新審視現(xiàn)在的體系
memcached支持外部存儲的難點是,網(wǎng)絡(luò)和事件處理相關(guān)的代碼(核心服務(wù)器)與 內(nèi)存存儲的代碼緊密關(guān)聯(lián)。這種現(xiàn)象也稱為tightly coupled(緊密耦合)。 必須將內(nèi)存存儲的代碼從核心服務(wù)器中獨立出來,才能靈活地支持外部引擎。 因此,基于我們設(shè)計的API,memcached被重構(gòu)成下面的樣子:

重構(gòu)之后,我們與1.2.5版、二進制協(xié)議支持版等進行了性能對比,證實了它不會造成性能影響。
在考慮如何支持外部引擎加載時,讓memcached進行并行控制(concurrency control)的方案是最為容易的, 但是對于引擎而言,并行控制正是性能的真諦,因此我們采用了將多線程支持完全交給引擎的設(shè)計方案。
以后的改進,會使得memcached的應(yīng)用范圍更為廣泛。
總結(jié)
本次介紹了memcached的超時原理、內(nèi)部如何刪除數(shù)據(jù)等,在此之上又介紹了二進制協(xié)議和 外部引擎支持等memcached的最新發(fā)展方向。這些功能要到1.3版才會支持,敬請期待!
這是我在本連載中的最后一篇。感謝大家閱讀我的文章!
下次由長野來介紹memcached的應(yīng)用知識和應(yīng)用程序兼容性等內(nèi)容。
4. memcached的分布式算法
發(fā)表日:2008/7/23
作者:長野雅廣(Masahiro Nagano)
原文鏈接:http://gihyo.jp/dev/feature/01/memcached/0004
我是Mixi的長野。 第2次、 第3次 由前坂介紹了memcached的內(nèi)部情況。本次不再介紹memcached的內(nèi)部結(jié)構(gòu), 開始介紹memcached的分布式。
memcached的分布式
正如第1次中介紹的那樣, memcached雖然稱為“分布式”緩存服務(wù)器,但服務(wù)器端并沒有“分布式”功能。 服務(wù)器端僅包括 第2次、 第3次 前坂介紹的內(nèi)存存儲功能,其實現(xiàn)非常簡單。 至于memcached的分布式,則是完全由客戶端程序庫實現(xiàn)的。 這種分布式是memcached的最大特點。
memcached的分布式是什么意思?
這里多次使用了“分布式”這個詞,但并未做詳細解釋。 現(xiàn)在開始簡單地介紹一下其原理,各個客戶端的實現(xiàn)基本相同。
下面假設(shè)memcached服務(wù)器有node1~node3三臺, 應(yīng)用程序要保存鍵名為“tokyo”“kanagawa”“chiba”“saitama”“gunma” 的數(shù)據(jù)。

圖1 分布式簡介:準備
首先向memcached中添加“tokyo”。將“tokyo”傳給客戶端程序庫后, 客戶端實現(xiàn)的算法就會根據(jù)“鍵”來決定保存數(shù)據(jù)的memcached服務(wù)器。 服務(wù)器選定后,即命令它保存“tokyo”及其值。

圖2 分布式簡介:添加時
同樣,“kanagawa”“chiba”“saitama”“gunma”都是先選擇服務(wù)器再保存。
接下來獲取保存的數(shù)據(jù)。獲取時也要將要獲取的鍵“tokyo”傳遞給函數(shù)庫。 函數(shù)庫通過與數(shù)據(jù)保存時相同的算法,根據(jù)“鍵”選擇服務(wù)器。 使用的算法相同,就能選中與保存時相同的服務(wù)器,然后發(fā)送get命令。 只要數(shù)據(jù)沒有因為某些原因被刪除,就能獲得保存的值。

圖3 分布式簡介:獲取時
這樣,將不同的鍵保存到不同的服務(wù)器上,就實現(xiàn)了memcached的分布式。 memcached服務(wù)器增多后,鍵就會分散,即使一臺memcached服務(wù)器發(fā)生故障 無法連接,也不會影響其他的緩存,系統(tǒng)依然能繼續(xù)運行。
接下來介紹第1次 中提到的Perl客戶端函數(shù)庫Cache::Memcached實現(xiàn)的分布式方法。
Cache::Memcached的分布式方法
Perl的memcached客戶端函數(shù)庫Cache::Memcached是 memcached的作者Brad Fitzpatrick的作品,可以說是原裝的函數(shù)庫了。
該函數(shù)庫實現(xiàn)了分布式功能,是memcached標準的分布式方法。
根據(jù)余數(shù)計算分散
Cache::Memcached的分布式方法簡單來說,就是“根據(jù)服務(wù)器臺數(shù)的余數(shù)進行分散”。 求得鍵的整數(shù)哈希值,再除以服務(wù)器臺數(shù),根據(jù)其余數(shù)來選擇服務(wù)器。
下面將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 ]; # 根據(jù)余數(shù)選擇服務(wù)器
printf "%s => %s"n", $key, $server;
}
Cache::Memcached在求哈希值時使用了CRC。
首先求得字符串的CRC值,根據(jù)該值除以服務(wù)器節(jié)點數(shù)目得到的余數(shù)決定服務(wù)器。 上面的代碼執(zhí)行后輸入以下結(jié)果:
tokyo => node2
kanagawa => node3
chiba => node2
saitama => node1
gunma => node1
根據(jù)該結(jié)果,“tokyo”分散到node2,“kanagawa”分散到node3等。 多說一句,當選擇的服務(wù)器無法連接時,Cache::Memcached會將連接次數(shù) 添加到鍵之后,再次計算哈希值并嘗試連接。這個動作稱為rehash。 不希望rehash時可以在生成Cache::Memcached對象時指定“rehash => 0”選項。
根據(jù)余數(shù)計算分散的缺點
余數(shù)計算的方法簡單,數(shù)據(jù)的分散性也相當優(yōu)秀,但也有其缺點。 那就是當添加或移除服務(wù)器時,緩存重組的代價相當巨大。 添加服務(wù)器后,余數(shù)就會產(chǎn)生巨變,這樣就無法獲取與保存時相同的服務(wù)器, 從而影響緩存的命中率。用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并執(zhí)行。
首先,當服務(wù)器只有三臺時:
$ 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
結(jié)果如上,node1保存a、c、d、e……,node2保存g、i、k……, 每臺服務(wù)器都保存了8個到10個數(shù)據(jù)。
接下來增加一臺memcached服務(wù)器。
$ 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命中了。像這樣,添加節(jié)點后 鍵分散到的服務(wù)器會發(fā)生巨大變化。26個鍵中只有六個在訪問原來的服務(wù)器, 其他的全都移到了其他服務(wù)器。命中率降低到23%。在Web應(yīng)用程序中使用memcached時, 在添加memcached服務(wù)器的瞬間緩存效率會大幅度下降,負載會集中到數(shù)據(jù)庫服務(wù)器上, 有可能會發(fā)生無法提供正常服務(wù)的情況。
mixi的Web應(yīng)用程序運用中也有這個問題,導致無法添加memcached服務(wù)器。 但由于使用了新的分布式方法,現(xiàn)在可以輕而易舉地添加memcached服務(wù)器了。 這種分布式方法稱為 Consistent Hashing。
Consistent Hashing
關(guān)于Consistent Hashing的思想,mixi株式會社的開發(fā)blog等許多地方都介紹過, 這里只簡單地說明一下。
Consistent Hashing的簡單說明
Consistent Hashing如下所示:首先求出memcached服務(wù)器(節(jié)點)的哈希值, 并將其配置到0~232的圓(continuum)上。 然后用同樣的方法求出存儲數(shù)據(jù)的鍵的哈希值,并映射到圓上。 然后從數(shù)據(jù)映射到的位置開始順時針查找,將數(shù)據(jù)保存到找到的第一個服務(wù)器上。 如果超過232仍然找不到服務(wù)器,就會保存到第一臺memcached服務(wù)器上。

圖4 Consistent Hashing:基本原理
從上圖的狀態(tài)中添加一臺memcached服務(wù)器。余數(shù)分布式算法由于保存鍵的服務(wù)器會發(fā)生巨大變化 而影響緩存的命中率,但Consistent Hashing中,只有在continuum上增加服務(wù)器的地點逆時針方向的 第一臺服務(wù)器上的鍵會受到影響。

圖5 Consistent Hashing:添加服務(wù)器
因此,Consistent Hashing最大限度地抑制了鍵的重新分布。 而且,有的Consistent Hashing的實現(xiàn)方法還采用了虛擬節(jié)點的思想。 使用一般的hash函數(shù)的話,服務(wù)器的映射地點的分布非常不均勻。 因此,使用虛擬節(jié)點的思想,為每個物理節(jié)點(服務(wù)器) 在continuum上分配100~200個點。這樣就能抑制分布不均勻, 最大限度地減小服務(wù)器增減時的緩存重新分布。
通過下文中介紹的使用Consistent Hashing算法的memcached客戶端函數(shù)庫進行測試的結(jié)果是, 由服務(wù)器臺數(shù)(n)和增加的服務(wù)器臺數(shù)(m)計算增加服務(wù)器后的命中率計算公式如下:
(1 - n/(n+m)) * 100
支持Consistent Hashing的函數(shù)庫
本連載中多次介紹的Cache::Memcached雖然不支持Consistent Hashing, 但已有幾個客戶端函數(shù)庫支持了這種新的分布式算法。 第一個支持Consistent Hashing和虛擬節(jié)點的memcached客戶端函數(shù)庫是 名為libketama的PHP庫,由last.fm開發(fā)。
至于Perl客戶端,連載的第1次 中介紹過的Cache::Memcached::Fast和Cache::Memcached::libmemcached支持 Consistent Hashing。
兩者的接口都與Cache::Memcached幾乎相同,如果正在使用Cache::Memcached, 那么就可以方便地替換過來。Cache::Memcached::Fast重新實現(xiàn)了libketama, 使用Consistent Hashing創(chuàng)建對象時可以指定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開發(fā)的C函數(shù)庫libmemcached的Perl模塊。 libmemcached本身支持幾種分布式算法,也支持Consistent Hashing, 其Perl綁定也支持Consistent Hashing。
總結(jié)
本次介紹了memcached的分布式算法,主要有memcached的分布式是由客戶端函數(shù)庫實現(xiàn), 以及高效率地分散數(shù)據(jù)的Consistent Hashing算法。下次將介紹mixi在memcached應(yīng)用方面的一些經(jīng)驗, 和相關(guān)的兼容應(yīng)用程序。
5. memcached的應(yīng)用和兼容程序
發(fā)表日:2008/7/30
作者:長野雅廣(Masahiro Nagano)
原文鏈接:http://gihyo.jp/dev/feature/01/memcached/0005
我是Mixi的長野。memcached的連載終于要結(jié)束了。 到上次為止, 我們介紹了與memcached直接相關(guān)的話題,本次介紹一些mixi的案例和 實際應(yīng)用上的話題,并介紹一些與memcached兼容的程序。
mixi案例研究
mixi在提供服務(wù)的初期階段就使用了memcached。 隨著網(wǎng)站訪問量的急劇增加,單純?yōu)閿?shù)據(jù)庫添加slave已無法滿足需要,因此引入了memcached。 此外,我們也從增加可擴展性的方面進行了驗證,證明了memcached的速度和穩(wěn)定性都能滿足需要。 現(xiàn)在,memcached已成為mixi服務(wù)中非常重要的組成部分。

圖1 現(xiàn)在的系統(tǒng)組件
服務(wù)器配置和數(shù)量
mixi使用了許許多多服務(wù)器,如數(shù)據(jù)庫服務(wù)器、應(yīng)用服務(wù)器、圖片服務(wù)器、 反向代理服務(wù)器等。單單memcached就有將近200臺服務(wù)器在運行。 memcached服務(wù)器的典型配置如下:
- CPU:Intel Pentium 4 2.8GHz
- 內(nèi)存:4GB
- 硬盤:146GB SCSI
- 操作系統(tǒng):Linux(x86_64)
這些服務(wù)器以前曾用于數(shù)據(jù)庫服務(wù)器等。隨著CPU性能提升、內(nèi)存價格下降, 我們積極地將數(shù)據(jù)庫服務(wù)器、應(yīng)用服務(wù)器等換成了性能更強大、內(nèi)存更多的服務(wù)器。 這樣,可以抑制mixi整體使用的服務(wù)器數(shù)量的急劇增加,降低管理成本。 由于memcached服務(wù)器幾乎不占用CPU,就將換下來的服務(wù)器用作memcached服務(wù)器了。
memcached進程
每臺memcached服務(wù)器僅啟動一個memcached進程。分配給memcached的內(nèi)存為3GB, 啟動參數(shù)如下:
/usr/bin/memcached -p 11211 -u nobody -m 3000 -c 30720
由于使用了x86_64的操作系統(tǒng),因此能分配2GB以上的內(nèi)存。32位操作系統(tǒng)中, 每個進程最多只能使用2GB內(nèi)存。也曾經(jīng)考慮過啟動多個分配2GB以下內(nèi)存的進程, 但這樣一臺服務(wù)器上的TCP連接數(shù)就會成倍增加,管理上也變得復(fù)雜, 所以mixi就統(tǒng)一使用了64位操作系統(tǒng)。
另外,雖然服務(wù)器的內(nèi)存為4GB,卻僅分配了3GB,是因為內(nèi)存分配量超過這個值, 就有可能導致內(nèi)存交換(swap)。連載的第2次中 前坂講解過了memcached的內(nèi)存存儲“slab allocator”,當時說過,memcached啟動時 指定的內(nèi)存分配量是memcached用于保存數(shù)據(jù)的量,沒有包括“slab allocator”本身占用的內(nèi)存、 以及為了保存數(shù)據(jù)而設(shè)置的管理空間。因此,memcached進程的實際內(nèi)存分配量要比 指定的容量要大,這一點應(yīng)當注意。
mixi保存在memcached中的數(shù)據(jù)大部分都比較小。這樣,進程的大小要比 指定的容量大很多。因此,我們反復(fù)改變內(nèi)存分配量進行驗證, 確認了3GB的大小不會引發(fā)swap,這就是現(xiàn)在應(yīng)用的數(shù)值。
memcached使用方法和客戶端
現(xiàn)在,mixi的服務(wù)將200臺左右的memcached服務(wù)器作為一個pool使用。 每臺服務(wù)器的容量為3GB,那么全體就有了將近600GB的巨大的內(nèi)存數(shù)據(jù)庫。 客戶端程序庫使用了本連載中多次提到車的Cache::Memcached::Fast, 與服務(wù)器進行交互。當然,緩存的分布式算法使用的是 第4次介紹過的 Consistent Hashing算法。
應(yīng)用層上memcached的使用方法由開發(fā)應(yīng)用程序的工程師自行決定并實現(xiàn)。 但是,為了防止車輪再造、防止Cache::Memcached::Fast上的教訓再次發(fā)生, 我們提供了Cache::Memcached::Fast的wrap模塊并使用。
通過Cache::Memcached::Fast維持連接
Cache::Memcached的情況下,與memcached的連接(文件句柄)保存在Cache::Memcached包內(nèi)的類變量中。 在mod_perl和FastCGI等環(huán)境下,包內(nèi)的變量不會像CGI那樣隨時重新啟動, 而是在進程中一直保持。其結(jié)果就是不會斷開與memcached的連接, 減少了TCP連接建立時的開銷,同時也能防止短時間內(nèi)反復(fù)進行TCP連接、斷開 而導致的TCP端口資源枯竭。
但是,Cache::Memcached::Fast沒有這個功能,所以需要在模塊之外 將Cache::Memcached::Fast對象保持在類變量中,以保證持久連接。
package Gihyo::Memcached;
use strict;
use warnings;
use Cache::Memcached::Fast;
my @server_list = qw/192.168.1.1:11211 192.168.1.1:11211/;
my $fast; ## 用于保持對象
sub new {
my $self = bless {}, shift;
if ( !$fast ) {
$fast = Cache::Memcached::Fast->new({ servers => "@server_list });
}
$self->{_fast} = $fast;
return $self;
}
sub get {
my $self = shift;
$self->{_fast}->get(@_);
}
上面的例子中,Cache::Memcached::Fast對象保存到類變量$fast中。
公共數(shù)據(jù)的處理和rehash
諸如mixi的主頁上的新聞這樣的所有用戶共享的緩存數(shù)據(jù)、設(shè)置信息等數(shù)據(jù), 會占用許多頁,訪問次數(shù)也非常多。在這種條件下,訪問很容易集中到某臺memcached服務(wù)器上。 訪問集中本身并不是問題,但是一旦訪問集中的那臺服務(wù)器發(fā)生故障導致memcached無法連接, 就會產(chǎn)生巨大的問題。
連載的第4次 中提到,Cache::Memcached擁有rehash功能,即在無法連接保存數(shù)據(jù)的服務(wù)器的情況下, 會再次計算hash值,連接其他的服務(wù)器。
但是,Cache::Memcached::Fast沒有這個功能。不過,它能夠在連接服務(wù)器失敗時, 短時間內(nèi)不再連接該服務(wù)器的功能。
my $fast = Cache::Memcached::Fast->new({
max_failures => 3,
failure_timeout => 1
});
在failure_timeout秒內(nèi)發(fā)生max_failures以上次連接失敗,就不再連接該memcached服務(wù)器。 我們的設(shè)置是1秒鐘3次以上。
此外,mixi還為所有用戶共享的緩存數(shù)據(jù)的鍵名設(shè)置命名規(guī)則, 符合命名規(guī)則的數(shù)據(jù)會自動保存到多臺memcached服務(wù)器中, 取得時從中僅選取一臺服務(wù)器。創(chuàng)建該函數(shù)庫后,就可以使memcached服務(wù)器故障 不再產(chǎn)生其他影響。
memcached應(yīng)用經(jīng)驗
到此為止介紹了memcached內(nèi)部構(gòu)造和函數(shù)庫,接下來介紹一些其他的應(yīng)用經(jīng)驗。
通過daemontools啟動
通常情況下memcached運行得相當穩(wěn)定,但mixi現(xiàn)在使用的最新版1.2.5 曾經(jīng)發(fā)生過幾次memcached進程死掉的情況。架構(gòu)上保證了即使有幾臺memcached故障 也不會影響服務(wù),不過對于memcached進程死掉的服務(wù)器,只要重新啟動memcached, 就可以正常運行,所以采用了監(jiān)視memcached進程并自動啟動的方法。 于是使用了daemontools。
daemontools是qmail的作者DJB開發(fā)的UNIX服務(wù)管理工具集, 其中名為supervise的程序可用于服務(wù)啟動、停止的服務(wù)重啟等。
這里不介紹daemontools的安裝了。mixi使用了以下的run腳本來啟動memcached。
#!/bin/sh
if [ -f /etc/sysconfig/memcached ];then
. /etc/sysconfig/memcached
fi
exec 2>&1
exec /usr/bin/memcached -p $PORT -u $USER -m $CACHESIZE -c $MAXCONN $OPTIONS
監(jiān)視
mixi使用了名為“nagios”的開源監(jiān)視軟件來監(jiān)視memcached。
在nagios中可以簡單地開發(fā)插件,可以詳細地監(jiān)視memcached的get、add等動作。 不過mixi僅通過stats命令來確認memcached的運行狀態(tài)。
define command {
command_name check_memcached
command_line $USER1$/check_tcp -H $HOSTADDRESS$ -p 11211 -t 5 -E -s 'stats"r"nquit"r"n' -e 'uptime' -M crit
}
此外,mixi將stats目錄的結(jié)果通過rrdtool轉(zhuǎn)化成圖形,進行性能監(jiān)視, 并將每天的內(nèi)存使用量做成報表,通過郵件與開發(fā)者共享。
memcached的性能
連載中已介紹過,memcached的性能十分優(yōu)秀。我們來看看mixi的實際案例。 這里介紹的圖表是服務(wù)所使用的訪問最為集中的memcached服務(wù)器。

圖2 請求數(shù)

圖3 流量

圖4 TCP連接數(shù)
從上至下依次為請求數(shù)、流量和TCP連接數(shù)。請求數(shù)最大為15000qps, 流量達到400Mbps,這時的連接數(shù)已超過了10000個。 該服務(wù)器沒有特別的硬件,就是開頭介紹的普通的memcached服務(wù)器。 此時的CPU利用率為:

圖5 CPU利用率
可見,仍然有idle的部分。因此,memcached的性能非常高, 可以作為Web應(yīng)用程序開發(fā)者放心地保存臨時數(shù)據(jù)或緩存數(shù)據(jù)的地方。
兼容應(yīng)用程序
memcached的實現(xiàn)和協(xié)議都十分簡單,因此有很多與memcached兼容的實現(xiàn)。 一些功能強大的擴展可以將memcached的內(nèi)存數(shù)據(jù)寫到磁盤上,實現(xiàn)數(shù)據(jù)的持久性和冗余。 連載第3次 介紹過,以后的memcached的存儲層將變成可擴展的(pluggable),逐漸支持這些功能。
這里介紹幾個與memcached兼容的應(yīng)用程序。
- repcached
- 為memcached提供復(fù)制(replication)功能的patch。
- Flared
- 存儲到QDBM。同時實現(xiàn)了異步復(fù)制和fail over等功能。
- memcachedb
- 存儲到BerkleyDB。還實現(xiàn)了message queue。
- Tokyo Tyrant
- 將數(shù)據(jù)存儲到Tokyo Cabinet。不僅與memcached協(xié)議兼容,還能通過HTTP進行訪問。
Tokyo Tyrant案例
mixi使用了上述兼容應(yīng)用程序中的Tokyo Tyrant。Tokyo Tyrant是平林開發(fā)的 Tokyo Cabinet DBM的網(wǎng)絡(luò)接口。它有自己的協(xié)議,但也擁有memcached兼容協(xié)議, 也可以通過HTTP進行數(shù)據(jù)交換。Tokyo Cabinet雖然是一種將數(shù)據(jù)寫到磁盤的實現(xiàn),但速度相當快。
mixi并沒有將Tokyo Tyrant作為緩存服務(wù)器,而是將它作為保存鍵值對組合的DBMS來使用。 主要作為存儲用戶上次訪問時間的數(shù)據(jù)庫來使用。它與幾乎所有的mixi服務(wù)都有關(guān), 每次用戶訪問頁面時都要更新數(shù)據(jù),因此負荷相當高。MySQL的處理十分笨重, 單獨使用memcached保存數(shù)據(jù)又有可能會丟失數(shù)據(jù),所以引入了Tokyo Tyrant。 但無需重新開發(fā)客戶端,只需原封不動地使用Cache::Memcached::Fast即可, 這也是優(yōu)點之一。關(guān)于Tokyo Tyrant的詳細信息,請參考本公司的開發(fā)blog。
- mixi Engineers' Blog - Tokyo Tyrantによる耐高負荷DBの構(gòu)築
- mixi Engineers' Blog - Tokyo (Cabinet|Tyrant)の新機能
總結(jié)
到本次為止,“memcached全面剖析”系列就結(jié)束了。我們介紹了memcached的基礎(chǔ)、內(nèi)部結(jié)構(gòu)、 分散算法和應(yīng)用等內(nèi)容。讀完后如果您能對memcached產(chǎn)生興趣,就是我們的榮幸。 關(guān)于mixi的系統(tǒng)、應(yīng)用方面的信息,請參考本公司的開發(fā)blog。 感謝您的閱讀。