高效PHP Redis快取技術,可參考下步驟
是否想過PHP使用redis作為快取時,如何能:
● 前後台模組共用Model層;
● 但是,不能每個Model類都進行快取,這樣太浪費Redis資源;
● 前後台模組可以自由決定從資料庫還是從快取讀資料;
● 沒有冗餘程式碼;
● 使用方便。
● 這裡我們先展示實現的最終效果。
最終的程式碼和使用說明請移步Github:
https://github.com/yeszao/php-redis-cache。
馬上安裝使用命令:
$ composer install yeszao/cache
經過簡單設定就可以使用,請參看Github的README說明。
1 最終效果
假設在MVC框架中,model層有一個Book類和一個getById方法,如下:
class Book { public function getById($id) { return $id; } }
加入快取技術之後,原來方法的呼叫方式和返回的資料結構都不應該改變。
所以,我們希望,最後的效果應該是這樣的:
(new Book)->getById(100); // 原始的、不用快取的呼叫方式,還是原來的方式,一般是讀取資料庫的資料。 (new Book)->getByIdCache(100); // 使用快取的呼叫方式,快取鍵名為:app_models_book:getbyid: + md5(參數列) (new Book)->getByIdClear(100); // 刪除這個快取 (new Book)->getByIdFlush(); // 刪除 getById() 方法對應的所有快取,即刪除 app_models_book:getbyid:*。這個方法不需要引數。
這樣我們可以很清楚的明白自己在做什麼,同時又知道資料的來源函數,並且被參照方式完全統一,可謂一箭三鵰。
其實實現起來也比較簡單,就是使用PHP的魔術方法__call()方法。
2 __call()方法
這裡簡單說明一下__call方法的作用。
在PHP中,當我們存取一個不存在的類方法時,就會呼叫這個類的__call()方法。
(如果類方法不存在,又沒有寫__call()方法,PHP會直接報錯)
假設我們有一個Book類:
class Book { public function __call($name, $arguments) { echo '類Book不存在方法', $name, PHP_EOL; } public function getById($id) { echo '我的ID是', $id, PHP_EOL; } }
當呼叫存在的getById(50)方法時,程式列印:我的ID是50。
而如果呼叫不存在的getAge()方法時,程式就會執行到A類的__call()方法裡面,這裡會列印:類Book不存在方法getAge。
這就是__call的原理。
3 實現細節
接下來我們就利用__call()方法的這種特性,來實現快取策略。
從上面的例子,我們看到,__call()方法被呼叫時,會傳入兩個引數。
name:想要呼叫的方法名arguments:參數列
我們就可以在引數上面做文章。
還是以Book類為例,我們假設其原本結構如下:
class Book { public function __call($name, $arguments) { // 待填充內容 } public function getById($id) { return ['id' => $id, 'title' => 'PHP快取技術' . $id]; } }
開始之前,我們還確認Redis的連線,這是快取必須用到的,這裡我們寫個簡單的單例類:
class Common { private static $redis = null; public static function redis() { if (self::$redis === null) { self::$redis = new Redis('127.0.0.1'); self::$redis->connect('redis'); } return self::$redis; }
然後,我們開始填充__call()方法程式碼,具體說明請看註釋:
class Book { public function __call($name, $arguments) { // 因為我們主要是根據方法名的字尾決定具體操作, // 所以如果傳入的 $name 長度小於5,可以直接報錯 if (strlen($name) < 5) { exit('Method does not exist.'); } // 接著,我們擷取 $name,獲取原方法和要執行的動作, // 是cache、clear還是flush,這裡我們取了個巧,動作 // 的名稱都是5個字元,這樣擷取就非常高效。 $method = substr($name, 0, -5); $action = substr($name, -5); // 當前呼叫的類名稱,包括名稱空間的名稱 $class = get_class(); // 生成快取鍵名,$arguments稍後再加上 $key = sprintf('%s:%s:', str_replace('', '_', $class), $method); // 都用小寫好看點 $key = strtolower($key); switch ($action) { case 'Cache': // 快取鍵名加上$arguments $key = $key . md5(json_encode($arguments)); // 從Redis中讀取資料 $data = Common::redis()->get($key); // 如果Redis中有資料 if ($data !== false) { $decodeData = json_decode($data, JSON_UNESCAPED_UNICODE); // 如果不是JSON格式的資料,直接返回,否則返回json解析後的資料 return $decodeData === null ? $data : $decodeData; } // 如果Redis中沒有資料則繼續往下執行 // 如果原方法不存在 if (method_exists($this, $method) === false) { exit('Method does not exist.'); } // 呼叫原方法獲取資料 $data = call_user_func_array([$this, $method], $arguments); // 儲存資料到Redis中以便下次使用 Common::redis()->set($key, json_encode($data), 3600); // 結束執行並返回資料 return $data; break; case 'Clear': // 快取鍵名加上$arguments $key = $key . md5(json_encode($arguments)); return Common::redis()->del($key); break; case 'Flush': $key = $key . '*'; // 獲取所有符合 $class:$method:* 規則的快取鍵名 $keys = Common::redis()->keys($key); return Common::redis()->del($keys); break; default: exit('Method does not exist.'); } } // 其他方法 }
這樣就實現了我們開始時的效果。
4 實際使用時
在實際使用中,我們需要做一些改變,把這一段程式碼歸入一個類中,
然後在model層的基礎類別中參照這個類,再傳入Redis控制代碼、類物件、方法名和引數,
這樣可以降低程式碼的耦合,使用起來也更靈活。
完整的程式碼已經放在Github上,請參考文章開頭的參考地址。
以上就是PHP使用redis作為快取(高效技術)的詳細內容,更多請關注TW511.COM其它相關文章!