memcached 高性能服務器架構

1.memcached詳細介紹。

引用
通常的網頁緩存方式有動態緩存和靜態緩存等幾種,在ASP.NET中已經可以實現對頁面局部進行緩存,而使用memcached的緩存比 ASP.NET的局部緩存更加靈活,可以緩存任意的對象,不管是否在頁面上輸出。而memcached最大的優點是可以分佈式的部署,這對於大規模應用來 說也是必不可少的要求。
LiveJournal.com使用了memcached在前端進行緩存,取得了良好的效果,而像wikipedia,sourceforge等也採用了或即將採用memcached作爲緩存工具。memcached可以大規模網站應用發揮巨大的作用。

Memcached是什麼?
Memcached是高性能的,分佈式的內存對象緩存系統,用於在動態應用中減少數據庫負載,提升訪問速度。
Memcached由Danga Interactive開發,用於提升LiveJournal.com訪問速度的。LJ每秒動態頁面訪問量幾千次,用戶700萬。Memcached將數據庫負載大幅度降低,更好的分配資源,更快速訪問。

如何使用memcached-Server端?
在服務端運行:
# ./memcached -d -m 2048 -l 10.0.0.40 -p 11211
這將會啓動一個佔用2G內存的進程,並打開11211端口用於接收請求。由於32位系統只能處理4G內存的尋址,所以在大於4G內存使用PAE的32位服務器上可以運行2-3個進程,並在不同端口進行監聽。

如何使用memcached-Client端?
在應用端包含一個用於描述Client的Class後,就可以直接使用,非常簡單。
PHP Example:
$options["servers"] = array("192.168.1.41:11211", "192.168.1.42:11212");
$options["debug"] = false;
$memc = new MemCachedClient($options);
$myarr = array("one","two", 3);
$memc->set("key_one", $myarr);
$val = $memc->get("key_one");
print $val[0]."/n"; // prints 'one‘
print $val[1]."/n"; // prints 'two‘
print $val[2]."/n"; // prints 3

爲什麼不使用數據庫做這些?

暫且不考慮使用什麼樣的數據庫(MS-SQL, Oracle, Postgres, MysQL-InnoDB, etc..), 實現事務(ACID,Atomicity, Consistency, Isolation, and Durability )需要大量開銷,特別當使用到硬盤的時候,這就意味着查詢可能會阻塞。當使用不包含事務的數據庫(例如Mysql-MyISAM),上面的開銷不存在,但 讀線程又可能會被寫線程阻塞。
Memcached從不阻塞,速度非常快。

爲什麼不使用共享內存?
最初的緩存做法是在線程內對對象進行緩存,但這樣進程間就無法共享緩存,命中率非常低,導致緩存效率極低。後來出現了共享內存的緩存,多個進程或者線程共享同一塊緩存,但畢竟還是隻能侷限在一臺機器上,多臺機器做相同的緩存同樣是一種資源的浪費,而且命中率也比較低。
Memcached Server和Clients共同工作,實現跨服務器分佈式的全局的緩存。並且可以與Web Server共同工作,Web Server對CPU要求高,對內存要求低,Memcached Server對CPU要求低,對內存要求高,所以可以搭配使用。

Mysql 4.x的緩存怎麼樣?
Mysql查詢緩存不是很理想,因爲以下幾點:
當指定的表發生更新後,查詢緩存會被清空。在一個大負載的系統上這樣的事情發生的非常頻繁,導致查詢緩存效率非常低,有的情況下甚至還不如不開,因爲它對cache的管理還是會有開銷。
在32位機器上,Mysql對內存的操作還是被限制在4G以內,但memcached可以分佈開,內存規模理論上不受限制。
Mysql上的是查詢緩存,而不是對象緩存,如果在查詢後還需要大量其它操作,查詢緩存就幫不上忙了。
如果要緩存的數據不大,並且查詢的不是非常頻繁,這樣的情況下可以用Mysql 查詢緩存,不然的話memcached更好。

數據庫同步怎麼樣?
這裏的數據庫同步是指的類似Mysql Master-Slave模式的靠日誌同步實現數據庫同步的機制。
你可以分佈讀操作,但無法分佈寫操作,但寫操作的同步需要消耗大量的資源,而且這個開銷是隨着slave服務器的增長而不斷增長的。
下一步是要對數據庫進行水平切分,從而讓不同的數據分佈到不同的數據庫服務器組上,從而實現分佈的讀寫,這需要在應用中實現根據不同的數據連接不同的數據庫。
當這一模式工作後(我們也推薦這樣做),更多的數據庫導致更多的讓人頭疼的硬件錯誤。
Memcached可以有效的降低對數據庫的訪問,讓數據庫用主要的精力來做不頻繁的寫操作,而這是數據庫自己控制的,很少會自己阻塞 自己。

Memcached快嗎?

非常快,它使用libevent,可以應付任意數量打開的連接(使用epoll,而非poll),使用非阻塞網絡IO,分佈式散列對象到不同的服務器,查詢複雜度是O(1)。(於敦德)

參考資料:
Distributed Caching with Memcached | Linux Journal
http://www.danga.com/
http://www.linuxjournal.com/article/7451



2.memcached在freebsd下的安裝:

引用
步驟1:安裝memcached
代碼:
# cd /usr/ports/databases/memcached/
# make install

步驟2:啓動memcached
代碼:
#/usr/bin/memcached -d -m 128 -l 192.168.1.1 -p 11211 -u httpd
參數解釋:
-d 以守護程序(daemon)方式運行 memcached;
-m 設置 memcached 可以使用的內存大小,單位爲 M;
-l 設置監聽的 IP 地址,如果是本機的話,通常可以不設置此參數;
-p 設置監聽的端口,默認爲 11211,所以也可以不設置此參數;
-u 指定用戶,如果當前爲 root 的話,需要使用此參數指定用戶。

以上以完成了memcached的安裝及啓動!
使用時按要求填上IP與端口既可



3.memcached 的工作原理
首先 memcached 是以守護程序方式運行於一個或多個服務器中,隨時接受客戶端的連接操作,客戶端可以由各種語言編寫,目前已知的客戶端 API 包括 Perl/PHP/Python/Ruby/Java/C#/C 等等。PHP 等客戶端在與 memcached 服務建立連接之後,接下來的事情就是存取對象了,每個被存取的對象都有一個唯一的標識符 key,存取操作均通過這個 key 進行,保存到 memcached 中的對象實際上是放置內存中的,並不是保存在 cache 文件中的,這也是爲什麼 memcached 能夠如此高效快速的原因。注意,這些對象並不是持久的,服務停止之後,裏邊的數據就會丟失。
點擊在新窗口中瀏覽此圖片

4.安裝php對memcache支持模塊
有兩種方法可以使 PHP 作爲 memcached 客戶端,調用 memcached 的服務進行對象存取操作。

第一種,PHP 有一個叫做 memcache 的擴展,Linux 下編譯時需要帶上 –enable-memcache[=DIR] 選項,Window 下則在 php.ini 中去掉 php_memcache.dll 前邊的註釋符,使其可用。
http://pecl.php.net/package/memcache

除此之外,還有一種方法,可以避開擴展、重新編譯所帶來的麻煩,那就是直接使用 php-memcached-client。
http://nio.infor96.com/wp-content/uploads/2006/04/memcached-client.zip
本文選用第二種方式,雖然效率會比擴展庫稍差一些,但問題不大。

5.PHP memcached 應用示例

引用
首先 下載 memcached-client.php,在下載了 memcached-client.php 之後,就可以通過這個文件中的類“memcached”對 memcached 服務進行操作了。其實代碼調用非常簡單,主要會用到的方法有 add()、get()、replace() 和 delete(),方法說明如下:

add ($key, $val, $exp = 0)
往 memcached 中寫入對象,$key 是對象的唯一標識符,$val 是寫入的對象數據,$exp 爲過期時間,單位爲秒,默認爲不限時間;

get ($key)
從 memcached 中獲取對象數據,通過對象的唯一標識符 $key 獲取;

replace ($key, $value, $exp=0)
使用 $value 替換 memcached 中標識符爲 $key 的對象內容,參數與 add() 方法一樣,只有 $key 對象存在的情況下才會起作用;

delete ($key, $time = 0)
刪除 memcached 中標識符爲 $key 的對象,$time 爲可選參數,表示刪除之前需要等待多長時間。

下面是一段簡單的測試代碼,代碼中對標識符爲 'mykey' 的對象數據進行存取操作:



//    包含 memcached 類文件
require_once('memcached-client.php');
//    選項設置
$options = array(
   'servers' => array('192.168.1.1:11211'), //memcached 服務的地址、端口,可用多個數組元素表示多個 memcached 服務
   'debug' => true,  //是否打開 debug
   'compress_threshold' => 10240,  //超過多少字節的數據時進行壓縮
   'persistant' => false  //是否使用持久連接
   );
//    創建 memcached 對象實例
$mc = new memcached($options);
//    設置此腳本使用的唯一標識符
$key = 'mykey';
//    往 memcached 中寫入對象
$mc->add($key, 'some random strings');
$val = $mc->get($key);
echo "n".str_pad('$mc->add() ', 60, '_')."n";
var_dump($val);
//    替換已寫入的對象數據值
$mc->replace($key, array('some'=>'haha', 'array'=>'xxx'));
$val = $mc->get($key);
echo "n".str_pad('$mc->replace() ', 60, '_')."n";
var_dump($val);
//    刪除 memcached 中的對象
$mc->delete($key);
$val = $mc->get($key);
echo "n".str_pad('$mc->delete() ', 60, '_')."n";
var_dump($val);
?>


是不是很簡單,在實際應用中,通常會把數據庫查詢的結果集保存到 memcached 中,下次訪問時直接從 memcached 中獲取,而不再做數據庫查詢操作,這樣可以在很大程度上減輕數據庫的負擔。通常會將 SQL 語句 md5() 之後的值作爲唯一標識符 key。下邊是一個利用 memcached 來緩存數據庫查詢結果集的示例(此代碼片段緊接上邊的示例代碼):


$sql = 'SELECT * FROM users';
$key = md5($sql);   //memcached 對象標識符
if ( !($datas = $mc->get($key)) ) {
   //    在 memcached 中未獲取到緩存數據,則使用數據庫查詢獲取記錄集。
   echo "n".str_pad('Read datas from MySQL.', 60, '_')."n";
   $conn = mysql_connect('localhost', 'test', 'test');
   mysql_select_db('test');
   $result = mysql_query($sql);
     while ($row = mysql_fetch_object($result))
       $datas[] = $row;
   //    將數據庫中獲取到的結果集數據保存到 memcached 中,以供下次訪問時使用。
   $mc->add($key, $datas);
} else {
     echo "n".str_pad('Read datas from memcached.', 60, '_')."n";
}
var_dump($datas);
?>


可以看出,使用 memcached 之後,可以減少數據庫連接、查詢操作,數據庫負載下來了,腳本的運行速度也提高了。



6.使用memcache情況,計數器、數據壓縮的例子

引用

使用情況一:統計 
//訪問統計
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect"); 
if($s=$memcache->get('a')) {
   $s=$s+1;
   $memcache->set('a',$s);
}
else
$memcache->set('a',1);
echo '訪問結果爲:'.$s;
?>

其實我們可以用increment方法代替上面的做法
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");

if($s=$memcache->increment('a',1)) {
   echo $s;    
}
else
$memcache->set('a',1);
?>



數據壓縮:
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect"); 
$test=(str_repeat('jetwong',100000));
$memcache->set('b',($test));
?>
使用壓縮:
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect"); 
$test=(str_repeat('jetwong',100000));
$memcache->set('b',($test),MEMCACHE_COMPRESSED);
?>


使用情況說明:

前臺比較 目前內存bytes 總共讀取bytes_read 總共寫入bytes_written

壓縮前 700085 700081 416

壓縮後 1131 1125 13

可能看到壓縮後明顯佔用內存少了不少



Memcache內存的更新清理(delete flush)
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");

/*設置值*/
$status = $memcache->getStats();
echo '設置前內存使用情況'.$status['bytes'].'
';
echo '設置後';
for($i=0;$i<9;$i++) {
   $memcache->set('b'.$i,rand(1,99));    
   echo '
'.$i.'->'.$memcache->get('b'.$i);       
}

/*查看設置的值*/
$status = $memcache->getStats();
echo 'delete前內存使用情況'.$status['bytes'].'
';
echo '
開始delete';
for($i=0;$i<9;$i++) {
   $memcache->delete('b'.$i);    
   echo '
'.$i.'->'.$memcache->get('b'.$i);
}

/*查看flush使用的情況*/
$status = $memcache->getStats();
echo '使用flush前內存使用情況'.$status['bytes'].'
';
echo '使用flush情況:';
for($i=0;$i<9;$i++) {
   $memcache->set('b'.$i,rand(1,99));    
   echo '
'.$i.'->'.$memcache->get('b'.$i);  
}
$memcache->flush();
echo 'flush之後:';
for($i=0;$i<9;$i++) {        
   echo '
'.$i.'->'.$memcache->get('b'.$i);
}
$status = $memcache->getStats();
echo 'flush後內存使用情況'.$status['bytes'].'
';
?>

內存超量的測試(set)

我們把內存設爲2M

./memcached -d -m 2 -p 11211 -u root

$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");

//600K左右
$test1= str_repeat('jetlee',100000);
//600K左右
$test2= str_repeat('jetlee',100000);
//600K左右
$test3= str_repeat('李連杰',200000);
//600K左右
$test4= str_repeat('連傑李',100000);
//200K
$test5= file_get_contents('http://img.pconline.com.cn/images/photoblog/2988177/20068/4/1154688770042_mthumb.JPG');
$test6= file_get_contents('http://img.pconline.com.cn/images/photoblog/1767557/20069/28/1159417108902_mthumb.jpg');

for($i=1;$i<=6;$i++) {
   $j='test'.$i;
   if($memcache->set($j,$$j)) {
       echo $j.'->設置成功
';
       $status = $memcache->getStats();
       echo '內存:'.$status['bytes'].'
';
   }
   else {
       echo $j.'->設置失敗
';
   }
}
?>

執行結果:

test1->設置成功
內存:600042
test2->設置成功
內存:1200084
test3->設置失敗
test4->設置成功
內存:1200084
test5->設置失敗
test6->設置失敗

剛好印證我們的計算,不過20萬的repeat爲什麼會失敗,不是太瞭解,,,,,,

總結:
示例:
//設置篇
if($data = $memcache->get('k',$v)) {
   //顯示我們的數據
   }
else {
   $data = get_from_database; //得到數據源
   if(!$memcache->set('k',$data), MEMCACHE_COMPRESSED) //開始設置
   log();    //不成功,記錄失敗信息    
}
?>


加我QQ:287363交流。

轉。。。。。。。。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章