是否想過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; } }
加入快取技術之后,原來方法的呼叫方式和回傳的資料結構都不應該改變,
所以,我們希望,最后的效果應該是這樣的:
1 (new Book)->getById(100); // 原始的、不用快取的呼叫方式,還是原來的方式,一般是讀取資料庫的資料, 2 (new Book)->getByIdCache(100); // 使用快取的呼叫方式,快取鍵名為:app_models_book:getbyid: + md5(引數串列) 3 (new Book)->getByIdClear(100); // 洗掉這個快取 4 (new Book)->getByIdFlush(); // 洗掉 getById() 方法對應的所有快取,即洗掉 app_models_book:getbyid:*,這個方法不需要引數,
這樣我們可以很清楚的明白自己在做什么,同時又知道資料的來源函式,并且被參考方式完全統一,可謂一箭三雕,
其實實作起來也比較簡單,就是使用PHP的魔術方法__call()方法,
2 __call()方法
這里簡單說明一下__call方法的作用,
在PHP中,當我們訪問一個不存在的類方法時,就會呼叫這個類的__call()方法,
(如果類方法不存在,又沒有寫__call()方法,PHP會直接報錯)
假設我們有一個Book類:
1 class Book 2 { 3 public function __call($name, $arguments) 4 { 5 echo '類Book不存在方法', $name, PHP_EOL; 6 } 7 8 public function getById($id) 9 { 10 echo '我的ID是', $id, PHP_EOL; 11 } 12 }
當呼叫存在的getById(50)方法時,程式列印:我的ID是50,
而如果呼叫不存在的getAge()方法時,程式就會執行到A類的__call()方法里面,這里會列印:類Book不存在方法getAge,
這就是__call的原理,
3 實作細節
接下來我們就利用__call()方法的這種特性,來實作快取策略,
從上面的例子,我們看到,__call()方法被呼叫時,會傳入兩個引數,
name:想要呼叫的方法名arguments:引數串列
我們就可以在引數上面做文章,
還是以Book類為例,我們假設其原本結構如下:
1 class Book 2 { 3 public function __call($name, $arguments) 4 { 5 // 待填充內容 6 } 7 8 public function getById($id) 9 { 10 return ['id' => $id, 'title' => 'PHP快取技術' . $id]; 11 } 12 }
開始之前,我們還確認Redis的連接,這是快取必須用到的,這里我們寫個簡單的單例類:
1 class Common 2 { 3 private static $redis = null; 4 5 public static function redis() 6 { 7 if (self::$redis === null) { 8 self::$redis = new \Redis('127.0.0.1'); 9 self::$redis->connect('redis'); 10 } 11 return self::$redis; 12 }
然后,我們開始填充__call()方法代碼,具體說明請看注釋:
1 class Book 2 { 3 public function __call($name, $arguments) 4 { 5 // 因為我們主要是根據方法名的后綴決定具體操作, 6 // 所以如果傳入的 $name 長度小于5,可以直接報錯 7 if (strlen($name) < 5) { 8 exit('Method does not exist.'); 9 } 10 11 // 接著,我們截取 $name,獲取原方法和要執行的動作, 12 // 是cache、clear還是flush,這里我們取了個巧,動作 13 // 的名稱都是5個字符,這樣截取就非常高效, 14 $method = substr($name, 0, -5); 15 $action = substr($name, -5); 16 17 // 當前呼叫的類名稱,包括命名空間的名稱 18 $class = get_class(); 19 20 // 生成快取鍵名,$arguments稍后再加上 21 $key = sprintf('%s:%s:', str_replace('\\', '_', $class), $method); 22 // 都用小寫好看點 23 $key = strtolower($key); 24 25 switch ($action) { 26 case 'Cache': 27 // 快取鍵名加上$arguments 28 $key = $key . md5(json_encode($arguments)); 29 30 // 從Redis中讀取資料 31 $data = https://www.cnblogs.com/a609251438/p/Common::redis()->get($key); 32 33 // 如果Redis中有資料 34 if ($data !== false) { 35 $decodeData =https://www.cnblogs.com/a609251438/p/ json_decode($data, JSON_UNESCAPED_UNICODE); 36 // 如果不是JSON格式的資料,直接回傳,否則回傳json決議后的資料 37 return $decodeData =https://www.cnblogs.com/a609251438/p/== null ? $data : $decodeData; 38 } 39 40 // 如果Redis中沒有資料則繼續往下執行 41 42 // 如果原方法不存在 43 if (method_exists($this, $method) === false) { 44 exit('Method does not exist.'); 45 } 46 47 // 呼叫原方法獲取資料 48 $data = https://www.cnblogs.com/a609251438/p/call_user_func_array([$this, $method], $arguments); 49 50 // 保存資料到Redis中以便下次使用 51 Common::redis()->set($key, json_encode($data), 3600); 52 53 // 結束執行并回傳資料 54 return $data; 55 break; 56 57 case 'Clear': 58 // 快取鍵名加上$arguments 59 $key = $key . md5(json_encode($arguments)); 60 return Common::redis()->del($key); 61 break; 62 63 case 'Flush': 64 $key = $key . '*'; 65 66 // 獲取所有符合 $class:$method:* 規則的快取鍵名 67 $keys = Common::redis()->keys($key); 68 return Common::redis()->del($keys); 69 break; 70 71 default: 72 exit('Method does not exist.'); 73 } 74 } 75 76 // 其他方法 77 }
這樣就實作了我們開始時的效果,
4 實際使用時
在實際使用中,我們需要做一些改變,把這一段代碼歸入一個類中,
然后在model層的基類中參考這個類,再傳入Redis句柄、類物件、方法名和引數,
這樣可以降低代碼的耦合,使用起來也更靈活,
完整的代碼已經放在Github上,請參考文章開頭的參考地址,
推薦閱讀:
PHP操作Redis資料庫常用方法
Redis的面試問題總結,面試跳槽必備
用PHP+Redis實作延遲任務,實作自動取消訂單
PHP基于Redis實作輕量級延遲佇列
php+redis實作注冊、洗掉、編輯、分頁、登錄、關注等功能
PHP 面試官問:你說說Redis的幾個過期策略?
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/102120.html
標籤:PHP
