PHP+Redis快取技術一覽

語言: CN / TW / HK

點選進入“PHP開源社群”    

免費獲取進階面試、文件、影片資源

有否想過PHP使用 redis 作為快取時,如何能:

1.前後臺模組共用Model層;

2. 但是,不能每個Model類都進行快取,這樣太浪費Redis資源;

3. 前後臺模組可以自由決定從資料庫還是從快取讀資料;

4. 沒有冗餘程式碼;

5. 使用方便。

這裡我們先展示實現的最終效果。

最終的程式碼和使用說明請移步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;

}

}

當呼叫 存在的 getName(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控制代碼、類物件、方法名和引數,

這樣可以降低程式碼的耦合,使用起來也更靈活。

*宣告:本文於網路整理,版權歸原作者所有,如來源資訊有誤或侵犯權益,請聯絡我們刪除或授權事宜。

END

PHP開源社群

掃描關注  進入”PHP資料“

免費獲取進階

面試、文件、影片資源

點選“檢視原文”獲取更多