自己以前筆記,和大家分享
一、Memcache概述
出現的原因:隨着數據量的增大,訪問的集中,使得數據庫服務器的負擔加重,數據庫響應惡化,網站顯示延遲等
memcache:是高性能的分布式內存緩存服務器.通過緩存數據庫的查詢結果,減少數據庫的訪問次數,以提高web應用的速度,提高可擴展性.緩存方式是將緩存結果存儲在內存中,通過內存來維護一個hash表.
Memcache是一個c/s軟件,默認間通過端口為11211
二、Memcache工作原理
memcached是以守護程序方式運行於一個或多個服務器中,隨時會接收客戶端的連接和操作。
原理:
第一次:web應用訪問數據庫,將查詢的結果顯示到應用的頁面,並將其查詢結果放入memcache中緩存
第二次:web訪問mecache服務器,如果有數據,直接顯示到web應用,否則查詢數據庫,顯示,在進行緩存到memcahe
三、為什么要在WEB中使用Memcache
原因:數據量的增大,訪問的集中,使得數據庫服務器的負擔加重,數據庫響應惡化,網站顯示延遲等,
解決方法:這時就需要減少服務器的壓力,減少數據庫檢索次數,可以建立數據庫和web應用的中間緩存層來處理
memcache作為高速運行的分布式內存緩存服務器,具有以下幾點,完全滿足需求:
1 本身是開源的,占用資源小,協議簡單的軟件,將數據庫和web之間的數據緩存,減少數據庫的檢索次數,減少數據庫的i/o
2 基於livevent的時間處理,因為libevent庫將linux,bsd,solaris等這些操作系統上的kqueue等時間處理功能功能封裝成統一接口,面對連接數增加,也能在linux,bsd,solaris等操作系統上發揮其高性能(i/o).
3 存儲方式:內置於內存存儲方式,存取的效率高,執行的速度快
4 memcache不互相通信的分布式:同個客戶端使得key有規律的封裝,實現memcache實現分布式,采用多台cached服務器,增加緩存的橫向延伸
四、安裝Memcache服務器(Linux和Window上分別安裝)
Linux下
1 安裝libevent時
./configure –with-libevent=/usr
Make && make install
2 安裝memcached
./configure –with-libevent=/usr
Make && make install
3 啟動Memcahced –d –m 128 –l 192.168.1.111 –p 11211 –u root
停止: kill `cat /tmp/memcached.pid`;
Killall memcached
Windows下
Memcahced.exe -d install [uninstall]
Memcached.exe –d -m 50 –l 127.0.0.1 -p 11211 start
五、Memcached服務器的管理(啟動)
Linux下啟動memcached
# /usr/local/bin/memcached -d -m 2048 -u root -l 192.168.1.20 -p 12111 -c 1024 -P /tmp/memcached.pid
參數說明:
-d 啟動為守護進程
-m <num> 分配給Memcached使用的內存數量,單位是MB,默認為64MB
-u <username> 運行Memcached的用戶,僅當作為root運行時
-l <ip_addr> 監聽的服務器IP地址,默認為環境變量INDRR_ANY的值
-p <num> 設置Memcached監聽的端口,最好是1024以上的端口
-c <num> 設置最大並發連接數,默認為1024
-P <file> 設置保存Memcached的pid文件,與-d選擇同時使用
Windows下安裝. 然后開始 memcached -d start
memcached的基本設置:
-p 監聽的端口
-l 連接的IP地址, 默認是本機
-d start 啟動memcached服務
-d restart 重起memcached服務
-d stop|shutdown 關閉正在運行的memcached服務
-d install 安裝memcached服務
-d uninstall 卸載memcached服務
-u 以的身份運行 (僅在以root運行的時候有效)
-m 最大內存使用,單位MB。默認64MB ,最大好像2G
-M 內存耗盡時返回錯誤,而不是刪除項
-c 最大同時連接數,默認是1024
-f 塊大小增長因子,默認是1.25
-n 最小分配空間,key+value+flags默認是48
-h 顯示幫助
六、操作Memcached (命令行方式telnet作為客戶端)
Command
Description
Example
get
Reads a value
get mykey
set
Set a key unconditionally
set mykey 0 60 5
add
Add a new key
add newkey 0 60 5
replace
Overwrite existing key
replace key 0 60 5
append
Append data to existing key
append key 0 60 15
prepend
Prepend data to existing key
prepend key 0 60 15
incr
Increments numerical key value by given number
incr mykey 2
decr
Decrements numerical key value by given number
decr mykey 5
delete
Deletes an existing key
delete mykey
flush_all
Invalidate specific items immediately
flush_all
Invalidate all items in n seconds
flush_all 900
stats
Prints general statistics
Stats
Prints memory statistics
stats slabs
Prints memory statistics
stats malloc
Print higher level allocation statistics
stats items
stats detail
stats sizes
Resets statistics
stats reset
version
Prints server version.
version
verbosity
Increases log level
verbosity
quit
Terminate telnet session
quit
1 Memcache的協議的錯誤部分主要是三個錯誤提示之提示指令:
普通錯誤信息:ERROR\r\n
客戶端錯誤:CLIENT_ERROR <錯誤信息>\r\n
服務器端錯誤:SERVER_ERROR <錯誤信息>\r\n
2 [ 數據保存指令]
數據保存是基本的功能,就是客戶端通過命令把數據返回過來,服務器端接收后進行處理。
A 指令格式:<命令> <鍵> <標記> <有效期> <數據長度>\r\n
<鍵key> :就是保存在服務器上唯一的一個表示符
<標記flag> 一個16位的無符號整形,用來設置服務器端跟客戶端一些交互的操作
<有效期>是數據在服務器上的有效期限,如果是0,則數據永遠有效,單位是秒,Memcache服務器端會把一個數據的有效期設置為當前Unix時間+設置的有效時間
<數據長度>塊數據的長度,一般在這個個長度結束以后下一行跟着block data數據內容,發送完數據以后,客戶端一般等待服務器端的返回,服務器端的返回:數據保存成功(STORED\r\n),數據保存失敗(NOT_STORED\r\n),一般是因為服務器端這個數據key已經存在了
B 主要是三個儲存數據的三個命令, set, add, replace
set 命令是保存一個叫做key的數據到服務器上
add 命令是添加一個數據到服務器,但是服務器必須這個key是不存在的,能夠保證數據不會被覆蓋
replace 命令是替換一個已經存在的數據,如果數據不存在,就是類似set功能
3 [ 數據提取命令]
get指令,格式是:get <鍵>*\r\n
<鍵>key是是一個不為空的字符串組合,發送這個指令以后,等待服務器的返回。如果服務器端沒有任何數據,則是返回:END\r\n,證明沒有不存在這個key,沒有任何數據,如果存在數據,則返回指定格式:
VALUE <鍵> <標記> <數據長度>\r\n
4 [ 數據刪除指令]
delete <鍵> <超時時間>\r\n
<超時時間> - timeout
按照秒為單位,這個是個可選項,如果你沒有指定這個值,那么服務器上key數據將馬上被刪除,如果設置了這個值,那么數據將在超時時間后把數據清除,該項缺省值是0,就是馬上被刪除,刪除數據后,服務器端會返
DELETED\r\n:刪除數據成功
NOT_FOUND\r\n:這個key沒有在服務器上找到
flush_all指令
這個指令執行后,服務器上所有緩存的數據都被刪除,並且返回
5 [其他指令]
當前所有Memcache服務器運行的狀態信息:stats
如果只是想獲取部分項目的信息,可以指定參數,格式:
stats <參數>\r\n:這個指令將只返回指定參數的項目狀態信息。
當前版本信息:version;
退出:quit
//統計
stats items
stats sizes
96 1
stats slabs:機制分配,內存管理信息
Stats:
Pid
memcache服務器的進程ID
uptime
服務器已經運行的秒數
Time
服務器當前的unix時間戳
version
memcache版本
pointer_size
當前操作系統的指針大小(32位系統一般是32bit)
rusage_user
進程的累計用戶時間
rusage_system
進程的累計系統時間
curr_items
服務器當前存儲的items數量
Total_items
從服務器啟動以后存儲的items總數量
Bytes
當前服務器存儲items占用的字節數
curr_connections
當前打開着的連接數
Total_connections
從服務器啟動以后曾經打開過的連接數
connection_structures
服務器分配的連接構造數
cmd_get
get命令(獲取)總請求次數
cmd_set
set命令(保存)總請求次數
get_hits
總命中次數
get_misses
總未命中次數
evictions
為獲取空閑內存而刪除的items數(分配給memcache的空間用滿后需要刪除舊的items來得到空間分配給新的items)
Bytes_read
總讀取字節數(請求字節數)
Bytes_written
總發送字節數(結果字節數)
Limit_maxbytes
分配給memcache的內存大小(字節)
threads
當前線程數
在php里也可以用getStats()來查看。
七、如何遍歷memcache
1. <?php
2. $host='192.168.15.225';
3. $port=11211;
4. $mem=new Memcache();
5. $mem->connect($host,$port);
6. $items=$mem->getExtendedStats (‘items’);
7. $items=$items["$host:$port"]['items'];
8. for($i=0,$len=count($items);$i<$len;$i++){
9. $number=$items[$i]['number'];
10. $str=$mem->getExtendedStats ("cachedump",$number,0);
11. $line=$str["$host:$port"];
12. if( is_array($line) && count($line)>0){
13. foreach($line as $key=>$value){
14. echo $key.'=>';
15. print_r($mem->get($key));
16. echo "\r\n";
17. }
18. }
19. }
20. ?>
八、在PHP程序中使用Memcached
a 在PHP安裝Memcache擴展
b 在PHP什么地方使用memcache
一、 數據庫讀出來的數據(select)使用memcache處理
二、 在會話控制session中使用
c 實例
Memcache面向對象的常用接口包括:
Memcache::connect -- 打開一個到Memcache的連接
Memcache::pconnect -- 打開一個到Memcache的長連接
Memcache::close -- 關閉一個Memcache的連接
Memcache::set -- 保存數據到Memcache服務器上
Memcache::get -- 提取一個保存在Memcache服務器上的數據
Memcache::replace -- 替換一個已經存在Memcache服務器上的項目(功能類似Memcache::set)
Memcache::delete -- 從Memcache服務器上刪除一個保存的項目
Memcache::flush -- 刷新所有Memcache服務器上保存的項目(類似於刪除所有的保存的項目)
Memcache::getStats -- 獲取當前Memcache服務器運行的狀態
Memcache::addServer -- 分布式服務器添加一個服務器
//創建memcache對象
$mem=new Memcache;
//連接memcache服務器
$mem->connect('localhost','11211');
//長連接memcache服務器
//$mem->pconnect('localhost','11211');
/*添加多個服務器*/
//$mem->addServer('url','port');
//$mem->addServer('www.baidu.com','port');
//$mem->addServer('192.168.90.112','port');
if($mem->add('test','this is test',MEMCACHE_COMPRESSED,3600)){
echo '添加或修改數據成功<br>';
}else{
//輸出memcache服務器中的值
echo $mem->get('test').'<br>';
}
if($mem->set('test','lampbrother',MEMCACHE_COMPRESSED,3600)){
echo '修改數據成功<br>';
}
//存取記錄
$mem->add('kkk','vvvvvv');
echo $mem->get('kkk').'-----<br>';
//刪除一條記錄
$mem->delete('kkk');
echo $mem->get('kkk').'-----<br>';
/* 存儲對象 */
class Person{
private $name;
private $age;
function __construct($name,$age){
$this->name=$name;
$this->age=$age;
}
}
if($mem->add('mem_obj',new Person('張三',23))){
/*
* 對象的存數方式
* O:6:"Person":2:{s:12:"Personname";s:6:"寮犱笁";s:11:"Personage";i:23;}
* */
echo '添加數據成功';
}
//刪除所有記錄
$mem->flush();
/**************分割線***********/
echo '=====================<br>';
echo $mem->get('test').'<br>';
/*
//創建memcache對象
$memcache=new memcache();
//連接服務器端add(),或者增加新的memcache服務器addServer()
$mem->pconnect('localhost','11211');
//讀取數據
$data=$memcache->get('key_v');
//判斷是否存在
if($data){
//直接使用在memcache端獲得資源顯示到頁面
}else{
//不存在時,從數據庫去的資源顯示頁面
//並將獲得結果存入memcache服務器端
}
$mem->close();
注意:
1 同一個項目安裝多次時,key要有前綴來進行區分
2 一個項目中有多條相同的sql語句,可以使用sql語句key值,同種sql結果保證使用一次數據庫服務器,減少數據庫服務器壓力.防止大小寫等不必要的異常錯誤,進行大小寫轉換進行md5加密,可以保證32為一致性,同時減少了存儲容量.還可以使用字符串函數進行md5加密后截取,存儲容量更短
*/
復制代碼
1. //連接memcache
2. $m = new Memcache();
3. $m->connect('localhost', 11211);
4.
5. //連接數據庫的我就不寫了.
6.
7. $sql = 'SELECT * FROM users';
8. $key = md5($sql); //md5 SQL命令 作為 memcache的唯一標識符
9. $rows = $m->get($key); //先重memcache獲取數據
10.
11. if (!$rows) {
12. //如果$rows為false那么就是沒有數據咯, 那么就寫入數據
13. $res = mysql_query($sql);
14. $rows = array();
15. while ($row = mysql_fetch_array($res)) {
16. $rows[] = $row;
17. }
18. $m->add($key, $rows); //這里寫入重數據庫中獲取的數據, 可以設置緩存時間, 具體時間設置多少, 根據自己需求吧.
19. }
20.
21. var_dump($rows); //打印出數據
22.
//上面第一次運行程序時, 因為還沒有緩存數據, 所以會讀取一次數據庫, 當再次訪問程序時, 就直接重memcache獲取了.
九、Memcache的安全(不讓別人訪問)
[ 內網訪問]
最好把兩台服務器之間的訪問是內網形態的,一般是Web服務器跟Memcache服務器之間。普遍的服務器都是有兩塊網卡,一塊指向互聯網,一塊指向內網,那么就讓Web服務器通過內網的網卡來訪問Memcache服務器,我們Memcache的服務器上啟動的時候就監聽內網的IP地址和端口,內網間的訪問能夠有效阻止其他非法的訪問。
# memcached -d -m 1024 -u root -l 192.168.0.200 -p 11211 -c 1024 -P /tmp/memcached.pid
Memcache服務器端設置監聽通過內網的192.168.0.200的ip的11211端口,占用1024MB內存,並且允許最大1024個並發連接
[ 設置防火牆]
防火牆是簡單有效的方式,如果卻是兩台服務器都是掛在網的,並且需要通過外網IP來訪問Memcache的話,那么可以考慮使用防火牆或者代理程序來過濾非法訪問。
一般我們在Linux下可以使用iptables或者FreeBSD下的ipfw來指定一些規則防止一些非法的訪問,比如我們可以設置只允許我們的Web服務器來訪問我們Memcache服務器,同時阻止其他的訪問。
# iptables -F
# iptables -P INPUT DROP
# iptables -A INPUT -p tcp -s 192.168.0.2 --dport 11211 -j ACCEPT
# iptables -A INPUT -p udp -s 192.168.0.2 --dport 11211 -j ACCEPT
上面的iptables規則就是只允許192.168.0.2這台Web服務器對Memcache服務器的訪問,能夠有效的阻止一些非法訪問,相應的也可以增加一些其他的規則來加強安全性,這個可以根據自己的需要來做。
Session()跨域的解決方案:
1)使用數據庫來實現
2)自己寫server端,通過改寫session處理函數來請求
3)使用nfs等跨機存儲來保存session
4)使用memcache來保存
5)使用zend platform提供的解決方案
其中的1-4都是通過改用可以跨機的儲存機制,再使用session_set_save_handler()來實現
以下是一些我在使用memcache來實現時的一些記錄:
1)使用類來實現時,各回調函數都定義為靜態方法,在類的構造中使用session_set_save_handler注冊回調函數, 如:
session_set_save_handler(
array('memSession', 'open'),
array('memSession', 'close'),
array('memSession', 'read'),
array('memSession', 'write'),
array('memSession', 'destroy'),
array('memSession', 'gc')
);
memSession為類名,要使用session,則先new memSession,再session_start();
2)生存期和垃圾回收
memCache的set命令有生存期,即使用set命令添加值時,可加上lifetime,此時間可以作為session的生存期,用戶在此時間內沒有動作,則會失效,但有動作則不會失效(因為每一個腳本結束時,都會執行write和close,此時lifetime就會被更新了),當然,如果使用cookie傳遞SID,則控制SESSION生存期可以用:ini_set('session.cookie_lifetime',time)來設定,這其實是控制cookie的有效時間,如果session賴以生存的cookie消失了,當然session也就活不了,使用cookie_lifetime來控制的話,無論有無動作,都將在指定的時間后過時
gc是指垃圾回收,在session中是指清理過期的session數據,影響的參數有:
session.gc_maxlifetime 被視為垃圾前的生存期,超過此時間沒有動作,數據會被清走
注意的是,gc不是每次啟動會話都會被執行,而是由session.gc_probability 和 session.gc_divisor的比率決定的
結論:控制SESSION的生存期有幾種方法
一是cookie_lifttime,這種方式無論有無動作,都會在指定時間內銷毀
二是在read中根椐保存時間控制,此方法在有動作時時間會一直有效
三設定session.gc_probability 和 session.gc_divisor的比率為1(即每次會話都會啟用gc),再設定gc.maxlifetime來指定生存期,此方法也是在用戶有動作時時間一直有效
3)回調函數的執行時機
open 在運行session_start()時執行
read 在運行session_start()時執行,因為在session_start時,會去read當前session數據並寫入$_SESSION變量
destroy 在運行session_destroy()時執行
close 在腳本執行完成或調用session_write_close() 或 session_destroy()時被執行,即在所有session操作完成后被執行
gc 執行概率由session.gc_probability 和 session.gc_divisor的值決定,時機是在open,read之后,即session_start會相繼執行open,read和gc
write 此方法在腳本結束和使用session_write_close()強制提交SESSION數據時執行
結論:
session_start //執行open(啟動會話),read(讀取session數據至$_SESSION),gc(清理垃圾)
腳本中間所有對$_SESSION的操作均不會調用這些回調函數
session_destroy //執行destroy,銷毀當前session(一般是刪除相應的記錄或文件),相應地,此回調函數銷毀的只是session的數據,但此時
var_dump一下$_SESSION變量,仍然有值的,但此值不會在close后被write回去
session_write_close() //執行write和close,保存$_SESSION至存儲,如不手工使用此方法,則會在腳本結束時被自動執行
清晰了以上信息,將對你清楚了解SESSION的工作原理有很大的幫助...
4)直接使用memcache作session處理
在我寫了一系列的memcache來保存session的代碼后,無意中發現,可以直接在php.ini中設定使用memcache作為session處理,而無須另外編碼,方法是:
修改php.ini中的以下值
session.save_handler = memcache
session.save_path = 'tcp://host1:11211' #有多個時直接用","分隔即可
如果只想在特定的應用里使用memcache儲存session,可以使用ini_set的方法對以上兩個參數進行設定
要測試一下是否真正用上了memcache,可以先捕足到使用的PHPSESSID,再作為KEY用memcach去讀一下,就清楚了
這幾天做某個產品的時候遇到一個小問題,現象比較詭異
產品用了兩台分布式的memcached服務器
某一個計數器取回來的數偶爾會不對,最后定位在php memcache client的failover機制上面。
我們知道,在memcached分布式環境下,某一個key是通過hash計算,分配到某一個memcached上面的
如果php.ini里面 memcache.allow_failover = 1的時候,在分布式環境下,某一台memcached出問題的話,會自動到其他的memcached嘗試
就會出現上面的問題,原因如下:
這個key是hash到服務器A的,但是服務器A正好一瞬間連不上(網絡或者其他問題),PHP就會去另一台服務器B去嘗試。
經過很偶然發生的網絡問題和很多次increment操作,有可能兩台服務器上面都有這個key,而且值不一樣……
get的時候有可能取到不同的值
如果對數據一致性要求很嚴格的話,可以關掉這個參數 memcache.allow_failover = 0,嗯,問題解
memcache.allow_failover 一個布爾值,用於控制當連接出錯時 Memcache 擴展是否故障轉移到其他服務器上。默認值為 1 (true)。 memcache.max_failover_attempts 一個整型值,用於限制連接到持久性數據或檢索數據的服務器數目。如果 memcache.allow_failover 為 false,則將忽略此參數。默認值為 20。 memcache.chunk_size 一個整型值,用於控制數據傳輸的大小。默認值為 8192 字節 (8 KB),但是如果設置為 32768 (32 KB),則可以獲得更好的性能。 memcache.default_port 另一個整型值,用於設置連接到 Memcache 所使用的 TCP 端口。除非您修改它,否則默認值為無特權的高端口 11211。
session.save_handler = memcache
session.save_path = "tcp://host:port?persistent=1&weight=2&timeout=2&retry_interval=15,tcp://host2:port2"
Session_保存在memcache中:
class MemSession {
private static $handler=null;
private static $lifetime=null;
private static $time = null;
const NS='session_';
private static function init($handler){
self::$handler=$handler;
self::$lifetime=ini_get('session.gc_maxlifetime');
self::$time=time();
}
public static function start(Memcache $memcache){
self::init($memcache);
session_set_save_handler(
array(__CLASS__, 'open'),
array(__CLASS__, 'close'),
array(__CLASS__, 'read'),
array(__CLASS__, 'write'),
array(__CLASS__, 'destroy'),
array(__CLASS__, 'gc')
);
session_start();
}
public static function open($path, $name){
return true;
}
public static function close(){
return true;
}
public static function read($PHPSESSID){
$out=self::$handler->get(self::session_key($PHPSESSID));
if($out===false || $out == null)
return '';
return $out;
}
public static function write($PHPSESSID, $data){
$method=$data ? 'set' : 'replace';
return self::$handler->$method(self::session_key($PHPSESSID), $data, MEMCACHE_COMPRESSED, self::$lifetime);
}
public static function destroy($PHPSESSID){
return self::$handler->delete(self::session_key($PHPSESSID));
}
public static function gc($lifetime){
return true;
}
private static function session_key($PHPSESSID){
$session_key=self::NS.$PHPSESSID;
return $session_key;
}
}
$memcache=new Memcache;
$memcache->connect("localhost", 11211) or die("could not connect!");
MemSession::start($memcache);
1 只直接使用session_strat();
2 將session.save_handler=memcache
session.save_path = "tcp://host:port?persistent=1&weight=2&timeout=2&retry_interval=15,tcp://host2:port2"