1.為什么要使用memcache
?由于網(wǎng)站的高并發(fā)讀寫需求,傳統(tǒng)的關(guān)系型數(shù)據(jù)庫開始出現(xiàn)瓶頸,例如:
1)對數(shù)據(jù)庫的高并發(fā)讀寫:
關(guān)系型數(shù)據(jù)庫本身就是個龐然大物,處理過程非常耗時(如解析SQL語句,事務(wù)處理等)。如果對關(guān)系型數(shù)據(jù)庫進行高并發(fā)讀寫(每秒上萬次的訪問),那么它是無法承受的。
2)對海量數(shù)據(jù)的處理:
對于大型的SNS網(wǎng)站,每天有上千萬次的數(shù)據(jù)產(chǎn)生(如twitter, 新浪微博)。對于關(guān)系型數(shù)據(jù)庫,如果在一個有上億條數(shù)據(jù)的數(shù)據(jù)表種查找某條記錄,效率將非常低。
使用memcache能很好的解決以上問題。
在實際使用中,通常把數(shù)據(jù)庫查詢的結(jié)果保存到Memcache中,下次訪問時直接從memcache中讀取,而不再進行數(shù)據(jù)庫查詢操作,這樣就在很大程度上減少了數(shù)據(jù)庫的負擔。
保存在memcache中的對象實際放置在內(nèi)存中,這也是memcache如此高效的原因。
2.memcache的安裝和使用
這個網(wǎng)上有太多教程了,不做贅言。
3.基于libevent的事件處理
libevent是個程序庫,它將Linux的
epoll、BSD類操作系統(tǒng)的kqueue等事件處理功能 封裝成統(tǒng)一的接口。即使對服務(wù)器的連接數(shù)增加,也能發(fā)揮O(1)的性能。
?memcached使用這個libevent庫,因此能在Linux、BSD、Solaris等操作系統(tǒng)上發(fā)揮其高性能。?
參考:
- libevent:?http://www.monkey.org/~provos/libevent/
- The C10K Problem:?http://www.kegel.com/c10k.html
4.memcache使用實例:
[php] view plain
copy <?php??$mc?=?new?Memcache();??$mc->connect('127.0.0.1',?11211);????$uid?=?(int)$_GET['uid'];??$sql?=?"select?*?from?users?where?uid='uid'?";??$key?=?md5($sql);??if(!($data?=?$mc->get($key)))?{??????$conn?=?mysql_connect('localhost',?'test',?'test');??????mysql_select_db('test');??????$result?=?mysql_fetch_object($result);??????while($row?=?mysql_fetch_object($result))?{????????????$data[]?=?$row;??????}??????$mc->add($key,?$datas);??}????var_dump($datas);???>??
5.memcache如何支持高并發(fā)(此處還需深入研究)
memcache使用多路復(fù)用I/O模型,如(epoll, select等),傳統(tǒng)I/O中,系統(tǒng)可能會因為某個用戶連接還沒做好I/O準備而一直等待,知道這個連接做好I/O準備。這時如果有其他用戶連接到服務(wù)器,很可能會因為系統(tǒng)阻塞而得不到響應(yīng)。
而多路復(fù)用I/O是一種消息通知模式,用戶連接做好I/O準備后,系統(tǒng)會通知我們這個連接可以進行I/O操作,這樣就不會阻塞在某個用戶連接。因此,memcache才能支持高并發(fā)。
此外,memcache使用了多線程機制??梢酝瑫r處理多個請求。線程數(shù)一般設(shè)置為CPU核數(shù),這研報告效率最高。
6.使用Slab分配算法保存數(shù)據(jù)
slab分配算法的原理是:把固定大小(1MB)的內(nèi)存分為n小塊,如下圖所示:
slab分配算法把每1MB大小的內(nèi)存稱為一個slab頁,每次向系統(tǒng)申請一個slab頁,然后再通過分隔算法把這個slab頁分割成若干個小塊的chunk(如上圖所示),然后把這些chunk分配給用戶使用,分割算法如下(在slabs.c文件中):
(注:memcache的github項目地址:https://github.com/wusuopubupt/memcached)
[cpp] view plain
copy ?????void?slabs_init(const?size_t?limit,?const?double?factor,?const?bool?prealloc)?{??????int?i?=?POWER_SMALLEST?-?1;??????unsigned?int?size?=?sizeof(item)?+?settings.chunk_size;????????mem_limit?=?limit;????????if?(prealloc)?{????????????????????mem_base?=?malloc(mem_limit);??????????if?(mem_base?!=?NULL)?{??????????????mem_current?=?mem_base;??????????????mem_avail?=?mem_limit;??????????}?else?{??????????????fprintf(stderr,?"Warning:?Failed?to?allocate?requested?memory?in"??????????????????????"?one?large?chunk.\nWill?allocate?in?smaller?chunks\n");??????????}??????}????????memset(slabclass,?0,?sizeof(slabclass));????????while?(++i?<?POWER_LARGEST?&&?size?<=?settings.item_size_max?/?factor)?{????????????????????if?(size?%?CHUNK_ALIGN_BYTES)??????????????size?+=?CHUNK_ALIGN_BYTES?-?(size?%?CHUNK_ALIGN_BYTES);????????????slabclass[i].size?=?size;??????????slabclass[i].perslab?=?settings.item_size_max?/?slabclass[i].size;??????????size?*=?factor;??????????if?(settings.verbose?>?1)?{??????????????fprintf(stderr,?"slab?class?%3d:?chunk?size?%9u?perslab?%7u\n",??????????????????????i,?slabclass[i].size,?slabclass[i].perslab);??????????}??????}????????power_largest?=?i;??????slabclass[power_largest].size?=?settings.item_size_max;??????slabclass[power_largest].perslab?=?1;??????if?(settings.verbose?>?1)?{??????????fprintf(stderr,?"slab?class?%3d:?chunk?size?%9u?perslab?%7u\n",??????????????????i,?slabclass[i].size,?slabclass[i].perslab);??????}??????????????{??????????char?*t_initial_malloc?=?getenv("T_MEMD_INITIAL_MALLOC");??????????if?(t_initial_malloc)?{??????????????mem_malloced?=?(size_t)atol(t_initial_malloc);??????????}????????}????????if?(prealloc)?{??????????slabs_preallocate(power_largest);??????}??}??
上面代碼中的slabclass是一個類型為slabclass_t結(jié)構(gòu)的數(shù)組,其定義如下:
[cpp] view plain
copy typedef?struct?{??????unsigned?int?size;????????????unsigned?int?perslab;?????????void?**slots;?????????????????unsigned?int?sl_total;????????unsigned?int?sl_curr;?????????void?*end_page_ptr;???????????????unsigned?int?end_page_free;???????unsigned?int?slabs;???????????void?**slab_list;?????????????unsigned?int?list_size;???????unsigned?int?killing;????????size_t?requested;???}?slabclass_t;??
借用別人的一張圖說明slabclass_t結(jié)構(gòu):
由分割算法的源代碼可知,slab算法按照不同大小的chunk分割slab頁,而不同大小的chunk以factor(默認是1.25)倍增大。
使用memcache -u root -vv 命令查看內(nèi)存分配情況(8字節(jié)對齊):
找到大小最合適的chunk分配給請求緩存的數(shù)據(jù):
[cpp] view plain
copy ??????????unsigned?int?slabs_clsid(const?size_t?size)?{??????int?res?=?POWER_SMALLEST;????????if?(size?==?0)??????????return?0;??????while?(size?>?slabclass[res].size)???????????if?(res++?==?power_largest)???????????????????return?0;??????return?res;??}??
內(nèi)存分配:
(此處參考:http://slowsnail.com.cn/?p=20)
[cpp] view plain
copy static?void?*do_slabs_alloc(const?size_t?size,?unsigned?int?id)?{??????slabclass_t?*p;??????void?*ret?=?NULL;??????item?*it?=?NULL;?????????if?(id?<?POWER_SMALLEST?||?id?>?power_largest)?{??????????MEMCACHED_SLABS_ALLOCATE_FAILED(size,?0);??????????return?NULL;??????}?????????p?=?&slabclass[id];??????assert(p->sl_curr?==?0?||?((item?*)p->slots)->slabs_clsid?==?0);?????????if?(!?(p->sl_curr?!=?0?||?do_slabs_newslab(id)?!=?0))?{????????????????ret?=?NULL;??????}?else?if?(p->sl_curr?!=?0)?{??????????it?=?(item?*)p->slots;??????????p->slots?=?it->next;??????????if?(it->next)?it->next->prev?=?0;??????????p->sl_curr--;??????????ret?=?(void?*)it;??????}?????????if?(ret)?{??????????p->requested?+=?size;??????????MEMCACHED_SLABS_ALLOCATE(size,?id,?p->size,?ret);??????}?else?{??????????MEMCACHED_SLABS_ALLOCATE_FAILED(size,?id);??????}?????????return?ret;??}??
do_slabs_allc()函數(shù)首先嘗試從slot列表(被回收的chunk)中獲取可用的chunk,如果有可用的就返回,否則從空閑的chunk列表中獲取可用的chunk并返回。
刪除過期item:
延遲刪除過期item到查找時進行,可以提高memcache的效率,因為不必每時每刻檢查過期item,從而提高CPU工作效率
使用LRU(last recently used)算法淘汰數(shù)據(jù):
[cpp] view plain
copy ?????????if?(tails[id]?==?0)?{??????itemstats[id].outofmemory++;??????return?NULL;??}????for?(search?=?tails[id];?tries?>?0?&&?search?!=?NULL;?tries--,?search=search->prev)?{??????if?(search->refcount?==?0)?{???????????if?(search->exptime?==?0?||?search->exptime?>?current_time)?{??????????????itemstats[id].evicted++;??????????????itemstats[id].evicted_time?=?current_time?-?search->time;??????????????STATS_LOCK();??????????????stats.evictions++;??????????????STATS_UNLOCK();??????????}??????????do_item_unlink(search);??????????break;??????}??}??it?=?slabs_alloc(ntotal,?id);??if?(it?==?0)?{??????itemstats[id].outofmemory++;??????????????????tries?=?50;??????for?(search?=?tails[id];?tries?>?0?&&?search?!=?NULL;?tries--,?search=search->prev)?{??????????if?(search->refcount?!=?0?&&?search->time?+?10800?<?current_time)?{???????????????itemstats[id].tailrepairs++;??????????????search->refcount?=?0;??????????????do_item_unlink(search);??????????????break;??????????}??????}??????it?=?slabs_alloc(ntotal,?id);??????if?(it?==?0)?{??????????return?NULL;??????}??}??
從item列表的尾部開始遍歷,找到refcount==0的chunk,調(diào)用do_item_unlink()函數(shù)釋放掉,另外,search->time+10800<current_time(即最近3小時沒有被訪問過的item),也釋放掉--這就是LRU算法的原理。
附:阿里2014筆試題一道:
某緩存系統(tǒng)采用
LRU淘汰算法,假定緩存容量為4,并且初始為空,那么在順序訪問一下數(shù)據(jù)項的時候:1,5,1,3,5,2,4,1,2出現(xiàn)緩存直接命中的次數(shù)是?,最后緩存中即將準備淘汰的數(shù)據(jù)項是?
答案:3, 5
解答:
1調(diào)入內(nèi)存 15調(diào)入內(nèi)存 1 51調(diào)入內(nèi)存 5 1(命中 1,更新次序)3調(diào)入內(nèi)存 5 1 35調(diào)入內(nèi)存 1 3 5 (命中5)2調(diào)入內(nèi)存 1 3 5 24調(diào)入內(nèi)存(1最久未使用,淘汰1) 3 5 2 41調(diào)入內(nèi)存(3最久未使用,淘汰3) 5 2 4 12調(diào)入內(nèi)存 5 4 1 2(命中2) 因此,直接命中次數(shù)是3,最后緩存即將準備淘汰的數(shù)據(jù)項是5
總結(jié)
以上是生活随笔為你收集整理的深入理解Memcache原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。