深入了解Memcache原理
1.為什麼要使用memcache
由于網站的高并發讀寫需求,傳統的關系型資料庫開始出現瓶頸,例如:
1)對資料庫的高并發讀寫:
關系型資料庫本身就是個龐然大物,處理過程非常耗時(如解析SQL語句,事務處理等)。如果對關系型資料庫進行高并發讀寫(每秒上萬次的通路),那麼它是無法承受的。
2)對海量資料的處理:
對于大型的SNS網站,每天有上千萬次的蘇劇産生(如twitter, 新浪微網誌)。對于關系型資料庫,如果在一個有上億條資料的資料表種查找某條記錄,效率将非常低。
使用memcache能很好的解決以上問題。
在實際使用中,通常把資料庫查詢的結果儲存到Memcache中,下次通路時直接從memcache中讀取,而不再進行資料庫查詢操作,這樣就在很大程度上減少了資料庫的負擔。
儲存在memcache中的對象實際放置在記憶體中,這也是memcache如此高效的原因。
2.memcache的安裝和使用
這個網上有太多教程了,不做贅言。
3.基于libevent的事件處理
libevent是個程式庫,它将Linux的epoll、BSD類作業系統的kqueue等事件處理功能 封裝成統一的接口。即使對伺服器的連接配接數增加,也能發揮O(1)的性能。
memcached使用這個libevent庫,是以能在Linux、BSD、Solaris等作業系統上發揮其高性能。
參考:
- libevent: http://www.monkey.org/~provos/libevent/
- The C10K Problem: http://www.kegel.com/c10k.html
4.memcache使用執行個體:
<?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如何支援高并發
memcache使用多路複用I/O模型,如(epoll, select等),傳統I/O中,系統可能會因為某個使用者連接配接還沒做好I/O準備而一直等待,知道這個連接配接做好I/O準備。這時如果有其他使用者連接配接到伺服器,很可能會因為系統阻塞而得不到響應。
而多路複用I/O是一種消息通知模式,使用者連接配接做好I/O準備後,系統會通知我們這個連接配接可以進行I/O操作,這樣就不會阻塞在某個使用者連接配接。是以,memcache才能支援高并發。
此外,memcache使用了多線程機制。可以同時處理多個請求。線程數一般設定為CPU核數,這研報告效率最高。
6.使用Slab配置設定算法儲存資料
slab配置設定算法的原理是:把固定大小(1MB)的記憶體分為n小塊,如下圖所示:
slab配置設定算法把每1MB大小的記憶體稱為一個slab頁,每次向系統申請一個slab頁,然後再通過分隔算法把這個slab頁分割成若幹個小塊的chunk(如上圖所示),然後把這些chunk配置設定給使用者使用,分割算法如下(在slabs.c檔案中):
memset(slabclass, 0, sizeof(slabclass));
// 初始化每個slabclass_t的trunk大小和每個slab中trunk數量
// slabclass中每個slabclass_t的trunk大小增長為factor倍
// 注意 i 從索引 1 開始
while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {
/* Make sure items are always n-byte aligned */
if (size % CHUNK_ALIGN_BYTES) // 記憶體8位元組對齊
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);
}
}
// slabclass中最後一個slabclass_t的trunk大小設定為最大item大小
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);
}
....// 省略
}
上面代碼中的slabclass是一個類型為slabclass_t結構的數組,其定義如下:
typedef struct {
unsigned int size; /* sizes of items */
unsigned int perslab; /* how many items per slab */
void **slots; /* list of item ptrs */
unsigned int sl_total; /* size of previous array */
unsigned int sl_curr; /* first free slot */
void *end_page_ptr; /* pointer to next free item at end of page, or 0 */
unsigned int end_page_free; /* number of items remaining at end of last alloced page */
unsigned int slabs; /* how many slabs were allocated for this class */
void **slab_list; /* array of slab pointers */
unsigned int list_size; /* size of prev array */
unsigned int killing; /* index+1 of dying slab, or zero if none */
size_t requested; /* The number of requested bytes */
} slabclass_t;
由分割算法的源代碼可知,slab算法按照不同大小的chunk分割slab頁,而不同大小的chunk以factor(預設是1.25)倍增大。
使用memcache -vv 指令檢視記憶體配置設定情況(8位元組對齊):