新聞中心
有否想過(guò)PHP使用 redis 作為緩存時(shí),如何能:

1.前后臺(tái)模塊共用Model層;
2. 但是,不能每個(gè)Model類(lèi)都進(jìn)行緩存,這樣太浪費(fèi)Redis資源;
3. 前后臺(tái)模塊可以自由決定從數(shù)據(jù)庫(kù)還是從緩存讀數(shù)據(jù);
4. 沒(méi)有冗余代碼;
5. 使用方便。
這里我們先展示實(shí)現(xiàn)的最終效果。
最終的代碼和使用說(shuō)明請(qǐng)移步Github:
https://github.com/yeszao/php-redis-cache
馬上安裝使用命令:
$ composer install yeszao/cache
經(jīng)過(guò)簡(jiǎn)單配置就可以使用,請(qǐng)參看Github的README說(shuō)明。
1、最終效果
假設(shè)在MVC框架中, model 層有一個(gè) Book 類(lèi)和一個(gè) getById 方法,如下:
class Book
{
public function getById($id)
{
return $id;
}
}
加入緩存技術(shù)之后,原來(lái)方法的 調(diào)用方式 和 返回的數(shù)據(jù)結(jié)構(gòu) 都不應(yīng)該改變。
所以,我們希望,最后的效果應(yīng)該是這樣的:
(new Book)->getById(100); // 原始的、不用緩存的調(diào)用方式,還是原來(lái)的方式,一般是讀取數(shù)據(jù)庫(kù)的數(shù)據(jù)
(new Book)->getByIdCache(100); // 使用緩存的調(diào)用方式,緩存鍵名為:app_models_book:getbyid: + md5(參數(shù)列表)
(new Book)->getByIdClear(100); // 刪除這個(gè)緩存
(new Book)->getByIdFlush(); // 刪除 getById() 方法對(duì)應(yīng)的所有緩存,即刪除 app_models_book:getbyid:*。這個(gè)方法不需要參數(shù)。
這樣我們可以很清楚的明白自己在做什么,同時(shí)又知道數(shù)據(jù)的來(lái)源函數(shù),并且被引用方式完全統(tǒng)一,可謂一箭三雕。
其實(shí)實(shí)現(xiàn)起來(lái)也比較簡(jiǎn)單,就是使用PHP的魔術(shù)方法 __call() 方法。
2、__call()方法
這里簡(jiǎn)單說(shuō)明一下 __call 方法的作用。
在PHP中,當(dāng)我們?cè)L問(wèn)一個(gè)不存在的類(lèi)方法時(shí),就會(huì)調(diào)用這個(gè)類(lèi)的 __call() 方法。
(如果類(lèi)方法不存在,又沒(méi)有寫(xiě) __call() 方法,PHP會(huì)直接報(bào)錯(cuò))
假設(shè)我們有一個(gè) Book 類(lèi):
class Book
{
public function __call($name, $arguments)
{
echo '類(lèi)Book不存在方法', $name, PHP_EOL;
}
public function getById($id)
{
echo '我的ID是', $id, PHP_EOL;
}
}
當(dāng)調(diào)用 存在的 getName(50) 方法時(shí),程序打?。?我的ID是50 。
而如果調(diào)用 不存在的 getAge() 方法時(shí),程序就會(huì)執(zhí)行到A類(lèi)的 __call() 方法里面,這里會(huì)打印: 類(lèi)Book不存在方法getAge 。
這就是 __call 的原理。
3、實(shí)現(xiàn)細(xì)節(jié)
接下來(lái)我們就利用 __call() 方法的這種特性,來(lái)實(shí)現(xiàn)緩存策略。
從上面的例子,我們看到, __call() 方法被調(diào)用時(shí),會(huì)傳入兩個(gè)參數(shù)。
$name :想要調(diào)用的方法名
$arguments :參數(shù)列表
我們就可以在參數(shù)上面做文章。
還是以 Book 類(lèi)為例,我們假設(shè)其原本結(jié)構(gòu)如下:
class Book
{
public function __call($name, $arguments)
{
// 待填充內(nèi)容
}
public function getById($id)
{
return ['id' => $id, 'title' => 'PHP緩存技術(shù)' . $id];
}
}
開(kāi)始之前,我們還確認(rèn)Redis的連接,這是緩存必須用到的,這里我們寫(xiě)個(gè)簡(jiǎn)單的單例類(lèi):
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;
}
然后,我們開(kāi)始填充 __call() 方法代碼,具體說(shuō)明請(qǐng)看注釋?zhuān)?/p>
class Book
{
public function __call($name, $arguments)
{
// 因?yàn)槲覀冎饕歉鶕?jù)方法名的后綴決定具體操作,
// 所以如果傳入的 $name 長(zhǎng)度小于5,可以直接報(bào)錯(cuò)
if (strlen($name) < 5) {
exit('Method does not exist.');
}
// 接著,我們截取 $name,獲取原方法和要執(zhí)行的動(dòng)作,
// 是cache、clear還是flush,這里我們?nèi)×藗€(gè)巧,動(dòng)作
// 的名稱(chēng)都是5個(gè)字符,這樣截取就非常高效。
$method = substr($name, 0, -5);
$action = substr($name, -5);
// 當(dāng)前調(diào)用的類(lèi)名稱(chēng),包括命名空間的名稱(chēng)
$class = get_class();
// 生成緩存鍵名,$arguments稍后再加上
$key = sprintf('%s:%s:', str_replace('\\', '_', $class), $method);
// 都用小寫(xiě)好看點(diǎn)
$key = strtolower($key);
switch ($action) {
case 'Cache':
// 緩存鍵名加上$arguments
$key = $key . md5(json_encode($arguments));
// 從Redis中讀取數(shù)據(jù)
$data = Common::redis()->get($key);
// 如果Redis中有數(shù)據(jù)
if ($data !== false) {
$decodeData = json_decode($data, JSON_UNESCAPED_UNICODE);
// 如果不是JSON格式的數(shù)據(jù),直接返回,否則返回json解析后的數(shù)據(jù)
return $decodeData === null ? $data : $decodeData;
}
// 如果Redis中沒(méi)有數(shù)據(jù)則繼續(xù)往下執(zhí)行
// 如果原方法不存在
if (method_exists($this, $method) === false) {
exit('Method does not exist.');
}
// 調(diào)用原方法獲取數(shù)據(jù)
$data = call_user_func_array([$this, $method], $arguments);
// 保存數(shù)據(jù)到Redis中以便下次使用
Common::redis()->set($key, json_encode($data), 3600);
// 結(jié)束執(zhí)行并返回?cái)?shù)據(jù)
return $data;
break;
case 'Clear':
// 緩存鍵名加上$arguments
$key = $key . md5(json_encode($arguments));
return Common::redis()->del($key);
break;
case 'Flush':
$key = $key . '*';
// 獲取所有符合 $class:$method:* 規(guī)則的緩存鍵名
$keys = Common::redis()->keys($key);
return Common::redis()->del($keys);
break;
default:
exit('Method does not exist.');
}
}
// 其他方法
}
這樣就實(shí)現(xiàn)了我們開(kāi)始時(shí)的效果。
4、實(shí)際使用時(shí)
在實(shí)際使用中,我們需要做一些改變,把這一段代碼歸入一個(gè)類(lèi)中,
然后在model層的基類(lèi)中引用這個(gè)類(lèi),再傳入Redis句柄、類(lèi)對(duì)象、方法名和參數(shù),
這樣可以降低代碼的耦合,使用起來(lái)也更靈活。
網(wǎng)站欄目:PHP+Redis緩存技術(shù)一覽
標(biāo)題來(lái)源:http://www.5511xx.com/article/djhpgho.html


咨詢(xún)
建站咨詢(xún)
