緩衝的使用,緩衝使用
緩衝,提高訪問速度的利器。工作中主要用到的是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位元組,則返回的值是(232-1),計算後大於或等於0的直接返回;而元素之類型為非數字(或數字字串)的,先轉為數字再計算;
4. 當我們new一個Memcache對象並進行串連時,可以有兩種方式,一是connect方法:$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).'
'; // 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', $uid); $stmt = $this->pdo->query($sql); return $stmt->fetch(PDO::FETCH_ASSOC); } }
而一般來說,通常是在Model模型類中才對資料進行讀寫,因此再定義一個UserModel類,盡量從簡,不再去寫可能會有的Model公用公用基類。
include 'Dal.php'; include 'Cache.php'; class UserModel{ private static $instance = null; public $cache = null; public $db = null; // 緩衝鍵名部分,通過函數名稱及參數拼接 const cache_get_userinfo_uid = 'GET|USER|INFO|BY|UID|%s'; private function __construct(){ // 擷取對應類執行個體 $this->cache = Cache::getInstance(); $this->db = Dal::getInstance(); } public static function getInstance(){ if(self::$instance === null){ self::$instance = new self(); } return self::$instance; } /** 根據uid擷取使用者資訊 */ public function getUserInfoByUid($params, $timeout = 3600){ // 缺少必要參數uid,返回 if(empty($params['uid'])){ return null; } // 拼接緩衝鍵名 $key = sprintf(self::cache_get_userinfo_uid, $params['uid']); // 擷取快取資料 $data = json_decode($this->cache->get($key), true); echo 'cache=>'; var_dump($data); // 緩衝為空白 if(!$data){ $data = $this->db->getUserInfoByUid($params['uid']); if(empty($data)){ return null; } // 在資料庫中擷取到資料後,重新寫入緩衝 $this->cache->set($key, json_encode($data), $timeout); } else{ } return $data; } } // 測試 $data = UserModel::getInstance()->getUserInfoByUid(array('uid'=>17653), 5); echo 'UserModel=>'; var_dump($data);
這裡,我們要通過使用者的uid擷取一個使用者的資訊,這個查詢條件是uid,傳過來的必要參數是uid,本來我們就是要緩衝每個使用者的資料,以期在重複查詢時速度更快,因此在定義緩衝的鍵時,就可以考慮使用uid。但是單單使用這個uid不行,因為假如其他的表中也有根據uid查詢的,就會出現鍵名重複,造成了資料混亂,好我們使用的是getUserInfoByUid這個方法,可以把方法名也拼進去,因此在UserModel類中,定義了一個常量cache_get_userinfo_uid,它最後的%s就是為了拼接參數uid。但是,這樣可能還是不行,現在公司啟動了另外一個項目,也要用到這個表,某人寫的方法跟這個方法名完全一樣,畢竟函數名字又沒有專利,而且營運一般單獨拿伺服器出來作為緩衝使用,幾個項目公用一台伺服器,資料寫在一個緩衝池裡也正常,此時可以考慮再加個首碼---工程名稱(或說項目),因此,在Cache類中,又定義了一個常量prefix,給這個項目叫APP,這樣基本就可以保證不會衝突了。當然如果這是一個組的子項目,這個組還有其他項目,這個組叫Star的話,可以再將項目組名稱加在前面。
看看效果,第一次(左邊)運行讀取cache是空的,但是可以看到列印的緩衝鍵名,
第二次(右邊)cache就有資料了,因為我把列印緩衝鍵名的地方放在Cache類的set方法中,第二次直接讀到了快取資料,沒有去連資料庫,自然也沒有重設緩衝,所以沒列印cache key。
扯了半天就是要保證鍵名不衝突,也夾雜了一般在一個項目中怎樣使用緩衝,有幾個供參考的方式:
1. 當前參數,可以有幾個拼接幾個,這裡只舉了個uid,假如還有name, age等等,可以都放在後面,具體怎麼弄看個人喜好或者代碼規範;
2. 當前方法名稱,盡量接近當前方法名稱,拼在緩衝key中間;
3. 當前項目名稱作為首碼(可選),更安全;
4. 所在項目組名稱,再次作為首碼拼進來(一般用不到)。
然後就是,在Model模型類中,先取緩衝,取不到則讀資料庫,讀到後不要忘了寫入緩衝,設定好到期時間等細節,就這麼一個流程。
當然你還可以做得更加細緻,比如讀取到快取資料後,再檢測下它的到期時間,發現還有5秒就要到期了,於是我們重新給它設定為3000秒,這樣下次訪問時又能取到了,不至於下次又讀庫,頻繁地串連資料庫是很耗時的一件事。
不過我還是覺得Redis好用,Redis中的Hash表最好的啦>-_-<,但恰好機子上還沒配。
唔......不早了,洗洗睡~=_=
http://www.bkjia.com/PHPjc/950709.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/950709.htmlTechArticle緩衝的使用,緩衝使用 緩衝,提高訪問速度的利器。工作中主要用到的是memcache和redis,它們是B/S軟體,類似練習時裝在機子上的Apache,它...