緩存,提高訪問速度的利器。工作中主要用到的是memcache和redis,它們是B/S軟件,類似練習時裝在機子上的Apache,它也會監聽端口,可以在客戶端(如在cmd上通過telnet操作Memcache)直接敲各自對應的命令來存取值,自學時可以通過這樣的方式熟悉下原生命令,看看效果。它們常駐內存,得到數據后寫入內存(安裝軟件后會占用一片內存區域),設定數據的過期時間,用到時直接從內存中讀出來,因為是內存所以訪問速度上有數量級的提高。lz曾今寫了個非常簡單的后臺,完全不用緩存那種,那速度我自己看了都忍不住要咆哮:這么慢tm也能叫網頁?!可那時我年幼無知,不會用緩存,當然現在也不見得好到哪兒去,但愿那時前端妹子沒在心里暗自唾罵>3<......
memcache的使用較簡單,至少從命令上來看是這樣。在memcache中,存儲的方式是key->value,鍵-值對應的方式存儲,類似聯接數組的元素形式,可存儲的類型可以是數字、字符串、數組、對象等,項目中一般會將非字符串(數字可看成是數字字符串)類型的先進行json編碼或者序列化再存入緩存,取出來時則解碼或者反序列化。Memcache是php專門處理memcache緩存的類,封裝的主要方法是get、set、delete、flush、connect、close,即一個緩存必備的獲取、設置、刪除、清空、連接、關閉等。(中括號為可傳參數)
| add | key, var, [flag], [expire] | 添加一個鍵為key、值為var到服務端,可指定過期時間expire,0則永不過期,最長30天 |
| get | key, [flag] | 從服務端獲取某一或某些元素 |
| delete | key, [timeout] | 在指定timeout時間內刪除key對應元素,默認timeout為0立即刪除 |
| flush | 無 | 清空,即刪除所有元素 |
| connect | host, [port], [timeout] | 連接到memcache服務器host主機的port端口,它會在腳本執行完自動關閉,也可主動關閉 |
| close | 無 | 主動關閉memcache服務端連接,但不會關閉pconnect生成的連接 |
| set | key, var, [flag], [expire] | 存儲鍵為key、值為var的元素到服務端,key存在則覆蓋其值,不存在新add一個 |
| replace | key, var, [flag], [expire] | 查找鍵為key的元素,以值var替換,查不到會報錯 |
| increment | key, [value] | 在鍵為key的元素上增加值value,value默認為1,可能改變原值 |
| decrement | key, [value] | 在鍵為key的元素上減少值value,value默認為1,可能改變原值 |
| addServer | host, [port], [persistent], [weight], [timeout], ...... | 添加一個memcache服務到連接池(保存多個memcache連接的地方)中,即建立一個到host的連接,port為端口 |
| pconnect | host, [port], [timeout] | 建立到位于host主機的memcache服務器的持久連接,port為端口,腳本執行完或調用close也無法關閉這種連接 |
其中有幾點需要留意下:
1. set和replace的用法區別:set幾乎任何情況下,當然不是連接都失敗、內存不夠用等硬性原因,都是成功的,replace只是在已經存在的鍵的基礎上進行修改,鍵若不存在不成功;
2. connect方法建立的連接,要么腳本運行結束自己斷開,要么調用close()方法被斷開掉;
3. increment或者的decrement方法得到的數永遠是大于或等于0,如果確實計算后得到的值是小于0的則進行轉化,比如int類型,32位上占4字節,則返回的值是(231-1),計算后大于或等于0的直接返回;而元素之類型為非數字(或數字字符串)的,先轉為數字再計算;
4. 當我們new一個Memcache對象并進行連接時,可以有兩種方式(不討論持久連接),$mem=new Memcache; $mem->connect('127.0.0.1', 11211); 或者使用addserver方法:$mem->addServer('127.0.0.1', 11211); 后者以向已有的連接池中添加一個Memcache服務器連接的方式建立,效果一樣。
Redis,一款強大的緩存工具,得益于在國內門戶網站中的廣泛使用,功能上在不斷完善,現在有穩定的版本(貌似微軟github上最新64位的release 2.8版本,在我工作的機子上運行不起來...),方法比memcache多得多,作為開發使用到的主要是數據的存取,而其他的事物、通信協議、集群,在一般開發個后臺,不是對速度追求到極致的情況下,還是基本用不到。redis中有按操作分為以下幾種類型:
1. String:也是key-value對,主要也是對它的鍵對應的元素進行get、set、求長度、自增等操作,redis將添加的類型全存為字符串,即便純數字;

2. Hash:哈希表(散列表),就是數據結構中的字典,采用特定的散列算法,比如手機通訊錄有幾百人,以姓的第一個字母作為關鍵碼進行排序,這樣我們可以快速找到聯系人,這是通過特定的映射方式,將一個字母與姓名聯系起來。在redis中,創建hash表時,首先指定表名,你存在哪張表里邊,然后指定一個域(field,實際就是一個變量),和它對應的值,單用cmd操作就像下面這樣(在hash表hash1中存儲一個域username對應值Jack,redis會自動建立username到Jack的映射),一張hash表里邊可以存儲多個域和對應的變量;

3. List:列表,相當于數據結構的鏈表,通過它還可實現棧和隊列,前面的string(這名字取得很怪異)存儲一一對應的單個鍵值對,它則存多個值(沒有鍵),重點是作為一個列表結構,它可以分別從左右(表頭和表尾)進行插入和彈出變量值,求長度等一系列操作,這也是為什么稱為列表,下面lpush、rpush命令就是從表左邊和右邊插入變量hello和world;

4. Set:集合,顧名思義也可以存儲多個變量值(沒有鍵),只是符合數學上定義的集合的特性,比如集合中元素不重復,因此set中各個數據對的鍵名也不一樣,它也可以進行并、交、差等運算(下面命令是向集合mySet添加一個值var);

5. SortedSet:有序集合,可看做Set的特殊方式,在每一個SortedSet類型集合中,添加一個值(沒有鍵)時,都要給它們指定一個稱為score的變量值,有序有序,得有個字段來判定它的順序是不,就是這個玩意兒,下面命令創建一個有序集合stset,添加一個值google.com,它的score是10。其實猜也猜得到,既然叫有序集合,既然給定了score,基本確定可以用這個來進行某些排序操作,事實也是這樣;

6. Key:單獨對鍵進行操作,當然對其對應的元素值也有影響,如查看鍵對應元素的數據類型、鍵對應元素的存活時間、重置存活時間、返回所有鍵、正則形式查看相關鍵名、刪除鍵(元素也跟著沒了)等等。它不是數據類型,實際上,Redis把Hash表表名、List列表表名、集合Set名都稱為鍵,整個集合或者表是它的值。下面是type命令查看它存儲值得類型,它存儲的是List列表。

上面這些命令,找個參考文檔走一遍,很快就能玩熟,個人在這兒也只是泛泛而談。那么在實際項目中,通常是怎樣使用緩存呢?不往大的談,比如微博的緩存設計,這可能涉及到架構層面的東西了,單說作為一個有一定訪問量的后臺,又想使用緩存來提高速度的情況下。
一個重要的原則(個人目前碰得多的情況)是,以傳入的參數來拼鍵名,以這個鍵名來存取值。比如現在Model模型類里面有個方法:getUserInfoByUid($params),傳入包含uid字段的數組查詢這個用戶的信息,在這兒使用緩存,先取緩存數據,取不到則去查數據庫,然后重新加入緩存,最后返回結果數據,這也是使用緩存的一個通用流程。
以Memcache為例,先看代碼,定義一個Cache類:
/** * Memcache緩存類 */ define('CACHE_HOST', '127.0.0.1'); define('CACHE_PORT', 11211); class Cache{ PRivate static $instance = null; private $_cache = null; // 緩存操作對象 const prefix = 'APP|'; // 緩存鍵值的前綴,為該項目名稱 private function __construct(){ if($this->_cache === null){ try{ $this->_cache = new Memcache; if(!$this->_cache->connect(CACHE_HOST, CACHE_PORT)){ exit('connect failed'); } } catch(Exception $e){ echo 'ERROR: '.$e->getMessage(); } } } /** * 返回單例 */ public static function getInstance(){ if(self::$instance === null){ self::$instance = new self(); } return self::$instance; } /** * 生成鍵名 */ private function genKey($key){ return Cache::prefix.$key; } public function __destruct(){ if($this->_cache !== null){ return $this->_cache->close(); } } /** * 添加元素,設置過期時間 */ public function add($key, $val, $expire = 3600){ return $this->_cache->add($this->genKey($key), $val, 0, $expire); } /** * 重置元素 */ public function set($key, $val, $expire = 3600){ echo 'cache key: '.$this->genKey($key).'<br/>'; // test return $this->_cache->set($this->genKey($key), $val, 0, $expire); } /** * 獲取元素 */ public function get($key){ return $this->_cache->get($this->genKey($key)); } /** * 自增 */ public function increment($key, $val = 1){ return $this->_cache->increment($this->genKey($key), $val); } /** * 自減 */ public function decrement($key, $val = 1){ return $this->_cache->decrement($this->genKey($key), $val); } /** * 刪除元素 */ public function delete($key, $timeout = 0){ return $this->_cache->delete($this->genKey($key), $timeout); } /** * 刪除所有元素 */ public function flush(){ return $this->_cache->flush(); } }然后定義一個操作數據庫操作類:
/** * 數據庫操作 */ define('DB_NAME', 'test'); define('DB_HOST', '127.0.0.1'); define('DB_PORT', 3306); define('DB_USER', 'root'); define('DB_PASS', '1234'); class Dal{ private static $instance = null; public $pdo = null; private function __construct(){ try{ $dsn = 'MySQL:dbname='.DB_NAME.';host='.DB_HOST.':'.DB_PORT; $this->pdo = new PDO($dsn, DB_USER, DB_PASS); } catch(PDOException $e){ echo 'Error: '.$e->getMessage(); return false; } } //獲取實例 public static function getInstance(){ if(self::$instance === null){ self::$instance = new self(); } return self::$instance; } // 獲取用戶信息 public function getUserInfoByUid($uid){ $sql = sprintf('select * from tab1 where uid = %s limit 1', $ui
新聞熱點
疑難解答