Memcached是danga.com(運(yùn)營(yíng)LiveJournal的技術(shù)團(tuán)隊(duì))開發(fā)的一套分布式內(nèi)存對(duì)象緩存系統(tǒng),用于在動(dòng)態(tài)系統(tǒng)中減少數(shù)據(jù)庫(kù)負(fù)載,提升性能。LJ每秒動(dòng)態(tài)頁(yè)面訪問量幾千次,用戶700萬。Memcached將數(shù)據(jù)庫(kù)負(fù)載大幅度降低,更好的分配資源,更快速訪問。
關(guān)于這個(gè)東西,相信很多人都用過,本文意在通過對(duì)memcached的實(shí)現(xiàn)及代碼分析,獲得對(duì)這個(gè)出色的開源軟件更深入的了解,并可以根據(jù)我們的需要對(duì)其進(jìn)行更進(jìn)一步的優(yōu)化。末了將通過對(duì)BSM_Memcache擴(kuò)展的分析,加深對(duì) memcached的使用方式理解。。
1.Memcached是什么
在闡述這個(gè)問題之前,我們首先要清楚它“不是什么”。很多人把它當(dāng)作和SharedMemory那種形式的存儲(chǔ)載體來使用,雖然memcached使用了同樣的“Key=>Value”方式組織數(shù)據(jù),但是它和共享內(nèi)存、APC等本地緩存有非常大的區(qū)別。Memcached是分布式的,也就是說它不是本地的。它基于網(wǎng)絡(luò)連接(當(dāng)然它也可以使用localhost)方式完成服務(wù),本身它是一個(gè)獨(dú)立于應(yīng)用的程序或守護(hù)進(jìn)程(Daemon方式)。即Memcached是高性能的,分布式的內(nèi)存對(duì)象緩存系統(tǒng),用于在動(dòng)態(tài)應(yīng)用中減少數(shù)據(jù)庫(kù)負(fù)載,提升訪問速度。
Memcached 使用libevent庫(kù)實(shí)現(xiàn)網(wǎng)絡(luò)連接服務(wù),理論上可以處理無限多的連接,但是它和Apache不同,它更多的時(shí)候是面向穩(wěn)定的持續(xù)連接的,所以它實(shí)際的并發(fā)能力是有限制的。在保守情況下memcached的最大同時(shí)連接數(shù)為200,這和Linux線程能力有關(guān)系,這個(gè)數(shù)值是可以調(diào)整的。關(guān)于 libevent可以參考相關(guān)文檔。 Memcached內(nèi)存使用方式也和APC不同。APC是基于共享內(nèi)存和MMAP的,memcachd有自己的內(nèi)存分配算法和管理方式,它和共享內(nèi)存沒有關(guān)系,也沒有共享內(nèi)存的限制,通常情況下,每個(gè)memcached進(jìn)程可以管理2GB的內(nèi)存空間,如果需要更多的空間,可以增加進(jìn)程數(shù)。
2. Memcached適合什么場(chǎng)合
在很多時(shí)候,memcached都被濫用了,這當(dāng)然少不了對(duì)它的抱怨。我經(jīng)常在論壇上看見有人發(fā)貼,類似于“如何提高效率”,回復(fù)是“用memcached”,至于怎么用,用在哪里,用來干什么一句沒有。memcached不是萬能的,它也不是適用在所有場(chǎng)合。
Memcached 是“分布式”的內(nèi)存對(duì)象緩存系統(tǒng),那么就是說,那些不需要“分布”的,不需要共享的,或者干脆規(guī)模小到只有一臺(tái)服務(wù)器的應(yīng)用, memcached不會(huì)帶來任何好處,相反還會(huì)拖慢系統(tǒng)效率,因?yàn)榫W(wǎng)絡(luò)連接同樣需要資源,即使是UNIX本地連接也一樣。在我之前的測(cè)試數(shù)據(jù)中顯示, memcached本地讀寫速度要比直接PHP內(nèi)存數(shù)組慢幾十倍,而APC、共享內(nèi)存方式都和直接數(shù)組差不多。可見,如果只是本地級(jí)緩存,使用 memcached是非常不劃算的。
Memcached在很多時(shí)候都是作為數(shù)據(jù)庫(kù)前端cache使用的。因?yàn)樗葦?shù)據(jù)庫(kù)少了很多SQL解析、磁盤操作等開銷,而且它是使用內(nèi)存來管理數(shù)據(jù)的,所以它可以提供比直接讀取數(shù)據(jù)庫(kù)更好的性能,在大型系統(tǒng)中,訪問同樣的數(shù)據(jù)是很頻繁的, memcached可以大大降低數(shù)據(jù)庫(kù)壓力,使系統(tǒng)執(zhí)行效率提升。另外,memcached也經(jīng)常作為服務(wù)器之間數(shù)據(jù)共享的儲(chǔ)媒介,例如在SSO系統(tǒng)中保存系統(tǒng)單點(diǎn)登陸狀態(tài)的數(shù)據(jù)就可以保存在memcached中,被多個(gè)應(yīng)用共享。
需要注意的是,memcached使用內(nèi)存管理數(shù)據(jù),所以它是易失的,當(dāng)服務(wù)器重啟,或者memcached進(jìn)程中止,數(shù)據(jù)便會(huì)丟失,所以 memcached不能用來持久保存數(shù)據(jù)。很多人的錯(cuò)誤理解,memcached的性能非常好,好到了內(nèi)存和硬盤的對(duì)比程度,其實(shí)memcached使用內(nèi)存并不會(huì)得到成百上千的讀寫速度提高,它的實(shí)際瓶頸在于網(wǎng)絡(luò)連接,它和使用磁盤的數(shù)據(jù)庫(kù)系統(tǒng)相比,好處在于它本身非常“輕”,因?yàn)闆]有過多的開銷和直接的讀寫方式,它可以輕松應(yīng)付非常大的數(shù)據(jù)交換量,所以經(jīng)常會(huì)出現(xiàn)兩條千兆網(wǎng)絡(luò)帶寬都滿負(fù)荷了,memcached進(jìn)程本身并不占用多少CPU資源的情況。
通常的網(wǎng)頁(yè)緩存方式有動(dòng)態(tài)緩存和靜態(tài)緩存等幾種,在ASP.NET中已經(jīng)可以實(shí)現(xiàn)對(duì)頁(yè)面局部進(jìn)行緩存,而使用memcached的緩存比 ASP.NET的局部緩存更加靈活,可以緩存任意的對(duì)象,不管是否在頁(yè)面上輸出。而memcached最大的優(yōu)點(diǎn)是可以分布式的部署,這對(duì)于大規(guī)模應(yīng)用來說也是必不可少的要求。
LiveJournal.com使用了memcached在前端進(jìn)行緩存,取得了良好的效果,而像wikipedia,sourceforge等也采用了或即將采用memcached作為緩存工具。memcached可以大規(guī)模網(wǎng)站應(yīng)用發(fā)揮巨大的作用。
2.1 memcached 的工作原理
首先 memcached 是以守護(hù)程序方式運(yùn)行于一個(gè)或多個(gè)服務(wù)器中,隨時(shí)接受客戶端的連接操作,客戶端可以由各種語(yǔ)言編寫,目前已知的客戶端 API 包括 Perl/PHP/Python/Ruby/Java/C#/C 等等。PHP 等客戶端在與 memcached 服務(wù)建立連接之后,接下來的事情就是存取對(duì)象了,每個(gè)被存取的對(duì)象都有一個(gè)唯一的標(biāo)識(shí)符 key,存取操作均通過這個(gè) key 進(jìn)行,保存到 memcached 中的對(duì)象實(shí)際上是放置內(nèi)存中的,并不是保存在 cache 文件中的,這也是為什么 memcached 能夠如此高效快速的原因。注意,這些對(duì)象并不是持久的,服務(wù)停止之后,里邊的數(shù)據(jù)就會(huì)丟失。
.2 memcached 安裝
首先是下載 memcached 了,目前最新版本是 1.1.12,直接從官方網(wǎng)站即可下載到 memcached-1.1.12.tar.gz。除此之外,memcached 用到了 libevent,我下載的是 libevent-1.1a.tar.gz
接下來是分別將 libevent-1.1a.tar.gz 和 memcached-1.1.12.tar.gz 解開包、編譯、安裝:
# tar -xzf libevent-1.1a.tar.gz
# cd libevent-1.1a
# ./configure --prefix=/usr
# make
# make install
# cd ..
# tar -xzf memcached-1.1.12.tar.gz
# cd memcached-1.1.12
# ./configure --prefix=/usr
# make
# make install
安裝完成之后,memcached 應(yīng)該在 /usr/bin/memcached。
3.如何使用memcached-Server端?
在服務(wù)端運(yùn)行:
# ./memcached -d -m 2048 -l 10.0.0.40 -p 11211 -u httpd
-d 以守護(hù)程序(daemon)方式運(yùn)行 memcached;
-m 設(shè)置 memcached 可以使用的內(nèi)存大小,單位為 M;
-l 設(shè)置監(jiān)聽的 IP&nb
sp;地址,如果是本機(jī)的話,通常可以不設(shè)置此參數(shù);
-p 設(shè)置監(jiān)聽的端口,默認(rèn)為 11211,所以也可以不設(shè)置此參數(shù);
-u 指定用戶,如果當(dāng)前為 root 的話,需要使用此參數(shù)指定用戶。
這將會(huì)啟動(dòng)一個(gè)占用2G內(nèi)存的進(jìn)程,并打開11211端口用于接收請(qǐng)求。由于32位系統(tǒng)只能處理4G內(nèi)存的尋址,所以在大于4G內(nèi)存使用PAE的32位服務(wù)器上可以運(yùn)行2-3個(gè)進(jìn)程,并在不同端口進(jìn)行監(jiān)聽。
4. 如何使用memcached-Client端?
在應(yīng)用端包含一個(gè)用于描述Client的Class后,就可以直接使用,非常簡(jiǎn)單。
PHP Example:
$options["debug"] = false;
$memc = new MemCachedClient($options);
$myarr = array("one","two", 3);
$memc->set("key_one", $myarr);
$options["servers"] = array("192.168.1.41:11211", "192.168.1.42:11212");
$val = $memc->get("key_one");
print $val[0]."\n"; // prints 'one‘
print $val[1]."\n"; // prints 'two‘
print $val[2]."\n"; // prints 3
5.為什么不使用數(shù)據(jù)庫(kù)做這些?
暫且不考慮使用什么樣的數(shù)據(jù)庫(kù)(MS-SQL, Oracle, Postgres, MysQL-InnoDB, etc..), 實(shí)現(xiàn)事務(wù)(ACID,Atomicity, Consistency, Isolation, and Durability )需要大量開銷,特別當(dāng)使用到硬盤的時(shí)候,這就意味著查詢可能會(huì)阻塞。當(dāng)使用不包含事務(wù)的數(shù)據(jù)庫(kù)(例如Mysql-MyISAM),上面的開銷不存在,但讀線程又可能會(huì)被寫線程阻塞。Memcached從不阻塞,速度非常快。
6.為什么不使用共享內(nèi)存?
最初的緩存做法是在線程內(nèi)對(duì)對(duì)象進(jìn)行緩存,但這樣進(jìn)程間就無法共享緩存,命中率非常低,導(dǎo)致緩存效率極低。后來出現(xiàn)了共享內(nèi)存的緩存,多個(gè)進(jìn)程或者線程共享同一塊緩存,但畢竟還是只能局限在一臺(tái)機(jī)器上,多臺(tái)機(jī)器做相同的緩存同樣是一種資源的浪費(fèi),而且命中率也比較低。
Memcached Server和Clients共同工作,實(shí)現(xiàn)跨服務(wù)器分布式的全局的緩存。并且可以與Web Server共同工作,Web Server對(duì)CPU要求高,對(duì)內(nèi)存要求低,Memcached Server對(duì)CPU要求低,對(duì)內(nèi)存要求高,所以可以搭配使用。
7.Mysql 4.x的緩存怎么樣?
Mysql查詢緩存不是很理想,因?yàn)橐韵聨c(diǎn):
當(dāng)指定的表發(fā)生更新后,查詢緩存會(huì)被清空。在一個(gè)大負(fù)載的系統(tǒng)上這樣的事情發(fā)生的非常頻繁,導(dǎo)致查詢緩存效率非常低,有的情況下甚至還不如不開,因?yàn)樗鼘?duì)cache的管理還是會(huì)有開銷。
在32位機(jī)器上,Mysql對(duì)內(nèi)存的操作還是被限制在4G以內(nèi),但memcached可以分布開,內(nèi)存規(guī)模理論上不受限制。
Mysql上的是查詢緩存,而不是對(duì)象緩存,如果在查詢后還需要大量其它操作,查詢緩存就幫不上忙了。
如果要緩存的數(shù)據(jù)不大,并且查詢的不是非常頻繁,這樣的情況下可以用Mysql 查詢緩存,不然的話memcached更好。
8.數(shù)據(jù)庫(kù)同步怎么樣?
這里的數(shù)據(jù)庫(kù)同步是指的類似Mysql Master-Slave模式的靠日志同步實(shí)現(xiàn)數(shù)據(jù)庫(kù)同步的機(jī)制。
你可以分布讀操作,但無法分布寫操作,但寫操作的同步需要消耗大量的資源,而且這個(gè)開銷是隨著slave服務(wù)器的增長(zhǎng)而不斷增長(zhǎng)的。
下一步是要對(duì)數(shù)據(jù)庫(kù)進(jìn)行水平切分,從而讓不同的數(shù)據(jù)分布到不同的數(shù)據(jù)庫(kù)服務(wù)器組上,從而實(shí)現(xiàn)分布的讀寫,這需要在應(yīng)用中實(shí)現(xiàn)根據(jù)不同的數(shù)據(jù)連接不同的數(shù)據(jù)庫(kù)。
當(dāng)這一模式工作后(我們也推薦這樣做),更多的數(shù)據(jù)庫(kù)導(dǎo)致更多的讓人頭疼的硬件錯(cuò)誤。
Memcached可以有效的降低對(duì)數(shù)據(jù)庫(kù)的訪問,讓數(shù)據(jù)庫(kù)用主要的精力來做不頻繁的寫操作,而這是數(shù)據(jù)庫(kù)自己控制的,很少會(huì)自己阻塞 自己。
9.Memcached快嗎?
非常快,它使用libevent,可以應(yīng)付任意數(shù)量打開的連接(使用epoll,而非poll),使用非阻塞網(wǎng)絡(luò)IO,分布式散列對(duì)象到不同的服務(wù)器,查詢復(fù)雜度是O(1)。
10.memcached的相關(guān)抽象類
public abstract class BaseManager<T
extends BaseObject>
implements Manager<T>

{
private MemcachedClient memcached;
protected int default_cache_time_second = 3600;
public void setMemcached(MemcachedClient memcached)

{
this.memcached = memcached;
}
public MemcachedClient getMemcached()

{
return memcached;
}
public void setDefault_cache_time_second(
int default_cache_time_second)

{
this.default_cache_time_second = default_cache_time_second;
}
public Object getCacheValueFromMemcached(String key)

{
return getCacheValueFromMemcached(key,
null);
}
public Object getCacheValueFromMemcached(String key, Object obj)

{
Object new_obj =
null;
try 
{
new_obj = memcached.get(key);
}
catch (Exception e)

{
if (logger.isWarnEnabled())

{
logger.warn("Failed to get from MeMCache", e);
}
}
return (new_obj !=
null) ? new_obj : obj;
}
public void setCacheValueToMemcached(String cacheKey,
int time_to_live, Serializable obj)

{
if (
null != memcached.get(cacheKey))

{
memcached.replace(cacheKey, time_to_live, obj);
}
else 
{
memcached.add(cacheKey, time_to_live, obj);
}
}
}
11.service調(diào)用memcache的一個(gè)例子:
public List<Integer> getLastModifyAlbumMember(
int limit)

{
String cacheKey =
this.createCachekey(
new Object[]

{"schedule","lastmodifyalbumember",limit});
List list = (List)
this.getCacheValueFromMemcached(cacheKey);
List<Integer> list1 =
new ArrayList<Integer>();
if(
null!=list&&list.size()>0)

{
for (Object aList : list)

{
list1.add(Integer.parseInt(String.valueOf(aList)));
}
}
return list1;
}
參考資料:
http://www.danga.com/
http://www.linuxjournal.com/article/7451
-----------------------------------------------------
Silence, the way to avoid many problems;
Smile, the way to solve many problems;