高效PHP Redis緩存技術,可參考下步驟


是否想過PHP使用redis作為緩存時,如何能:

  1. 前后台模塊共用Model層;
  2. 但是,不能每個Model類都進行緩存,這樣太浪費Redis資源;
  3. 前后台模塊可以自由決定從數據庫還是從緩存讀數據;
  4. 沒有冗余代碼;
  5. 使用方便。
    這里我們先展示實現的最終效果。

最終的代碼和使用說明請移步Github:

馬上安裝使用命令:

$ 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 = Common::redis()->get($key);
32 
33                 // 如果Redis中有數據
34                 if ($data !== false) {
35                     $decodeData = json_decode($data, JSON_UNESCAPED_UNICODE);
36                     // 如果不是JSON格式的數據,直接返回,否則返回json解析后的數據
37                     return $decodeData === 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 = 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的幾個過期策略?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM