爲何須要Cache(緩存)?php
假設如今有一個小說網,有很是多的讀者,有一篇新的章節更新了,那麼可能一分鐘內有幾萬幾十萬的訪問量.web
若是沒有緩存,一樣的內容就要去數據庫重複查詢,那可能網站一下就掛掉了.redis
追求性能的web站點應該充分利用緩存,常見的緩存類型有File,Memcache,Redis等,這裏就不說他們的區別了thinkphp
今天咱們分析下TP5 Cache的內部實現原理.數據庫
首先看官方文檔如何使用緩存的.編程
如上圖,調用Cache類的的靜態方法set就能夠直接使用了,咱們查看Cache類文件 在application/thinkphp/library/think目錄下json
protected static $instance = []; public static $readTimes = 0; public static $writeTimes = 0; /** * 操做句柄 * @var object * @access protected */
protected static $handler; /** * 寫入緩存 * @access public * @param string $name 緩存標識 * @param mixed $value 存儲數據 * @param int|null $expire 有效時間 0爲永久 * @return boolean */
public static function set($name, $value, $expire = null) { self::$writeTimes++; return self::init()->set($name, $value, $expire); }
看到原來set方法是這樣的, 其中writeTimes 是Cache類的靜態變量,主要記錄緩存的讀取次數,這不是重點.數組
注意到了嗎,有個靜態變量命名爲 $instance, 上次說過這樣命名大機率就是 單例模式了.緩存
set方法的重點是init方法app
咱們再看init方法
public static function init(array $options = []) { if (is_null(self::$handler)) { // 自動初始化緩存
if (!empty($options)) { $connect = self::connect($options); } elseif ('complex' == Config::get('cache.type')) { $connect = self::connect(Config::get('cache.default')); } else { $connect = self::connect(Config::get('cache')); } self::$handler = $connect; } return self::$handler; }
handler就是操做的句柄(巨餅:-) ), 這裏一看,果真是單例模式了,若是句柄爲空纔去初始化對象,否則直接返回.句柄
一樣,這裏重點是connect函數, 傳入的參數是 配置信息
一樣,咱們查看connect方法
/** * 鏈接緩存 * @access public * @param array $options 配置數組 * @param bool|string $name 緩存鏈接標識 true 強制從新鏈接 * @return Driver */
public static function connect(array $options = [], $name = false) { $type = !empty($options['type']) ? $options['type'] : 'File'; if (false === $name) { $name = md5(serialize($options)); } if (true === $name || !isset(self::$instance[$name])) { $class = false !== strpos($type, '\\') ? $type : '\\think\\cache\\driver\\' . ucwords($type); // 記錄初始化信息
App::$debug && Log::record('[ CACHE ] INIT ' . $type, 'info'); if (true === $name) { return new $class($options); } else { self::$instance[$name] = new $class($options); } } return self::$instance[$name]; }
self::$instance[$name] = new $class($options); 這一句裏,咱們就能夠知道句柄的真實身份拉,
$class = false !== strpos($type, '\\') ? $type : '\\think\\cache\\driver\\' . ucwords($type);
這一句的意思是class的名字由type決定, 若是type沒有包含反斜線, 則class = \think\cache\driver\.ucwords($type)
thinkPhp 是把think做爲核心目錄的別名的,因此他真實路徑就是 \thinkphp\libray\\think\driver\.ucwords($type)
根據自動加載的尿性,天然是去該文件夾下加載對應的對象
(額外提一句,這利用的是PHP動態變量的一個特性,其實就和工廠模式一個原理,運行中動態決定實例化的對象)
type是什麼呢? type就是函數傳入的參數,也就是配置信息,咱們看下配置信息
type就是驅動方式,若是咱們type填寫的是File,那麼就使用文件驅動,實例化的是
\think\cache\driver\File.class
咱們看下 \think\cache\driver文件下有什麼文件,那就知道thinkphp爲咱們提供了多少種緩存驅動了
原來有這麼多!
點進去
每一個文件,咱們能夠發現一個共同點, 每一個類都是繼承了 抽象類 Driver
Driver決定了 每個Cache驅動應該是什麼樣子的,他們的方法基本是同樣的,而實現方式因每一個驅動不一樣而異
其實這就是 適配器模式,若是是咱們本身寫,固然不會寫那麼多拉,不過TP5是爲了造福廣大PHP開發者,因此編寫了那麼多不一樣的驅動供咱們使用.
咱們重點看Redis吧, 若是要去實驗,記得把 config中的 Cache.type更改成 redis
Redis類的方法不多,先看看構造函數
public function __construct($options = []) { if (!extension_loaded('redis')) { throw new \BadFunctionCallException('not support: redis'); } if (!empty($options)) { $this->options = array_merge($this->options, $options); } $func = $this->options['persistent'] ? 'pconnect' : 'connect'; $this->handler = new \Redis; $this->handler->$func($this->options['host'], $this->options['port'], $this->options['timeout']); if ('' != $this->options['password']) { $this->handler->auth($this->options['password']); } if (0 != $this->options['select']) { $this->handler->select($this->options['select']); } }
可見TP5的 redis驅動 是基於phpredis的阿, handler 就是實例化的phpredis類, 所以選了哪一個驅動,Cache的類天然就是哪些驅動.
因此說若是要使用 TP5的 redis,必需要先安裝phpredis擴展.
這裏就順便解析下 redis重寫的 set方法
/** * 寫入緩存 * @access public * @param string $name 緩存變量名 * @param mixed $value 存儲數據 * @param integer $expire 有效時間(秒) * @return boolean */ public function set($name, $value, $expire = null) { if (is_null($expire)) { $expire = $this->options['expire']; } if ($this->tag && !$this->has($name)) { $first = true; } $key = $this->getCacheKey($name); //對數組/對象數據進行緩存處理,保證數據完整性 byron sampson<xiaobo.sun@qq.com> $value = (is_object($value) || is_array($value)) ? json_encode($value) : $value; if (is_int($expire) && $expire) { $result = $this->handler->setex($key, $expire, $value); } else { $result = $this->handler->set($key, $value); } isset($first) && $this->setTagItem($key); return $result; }
本來的phpredis set方法 只能是 普通的鍵值對, 而重寫的set方法如今能夠是 鍵,數組啦,這是很是有用的方法
能夠看到實現的 原理是把 數組或者對象 序列化爲json, 取值的時候則反序列化成爲數組.
到這裏咱們就基本分析完了一個驅動是如何實現的,首先必須 繼承Driver類,實現Driver規定的方法,而後將handler交給Cache類去使用
咱們回到Cache類
能夠看到Cache類調用函數的方法基本鬥是這樣, init()獲取 到handler,而後操做handler對象,也就是咱們真正的 操做對象,這裏就是 phpredis類啦,
固然咱們是沒辦法直接操做 phpredis類的, 只能使用Cache類 的寥寥幾種方法,因此有些人不滿意,由於隊列,集和,哈希都認爲沒辦法使用了,我也在網上看到有些同窗 重寫TP5的 redis類
其實大可沒必要, Cache類仍是暴露了一個接口給咱們的.
咱們能夠這樣
$res = Cache::init(); $redis = $res->handler(); $redis->lpush('test',111); $redis->rpush('test',111); $redis->lpop('test');
得到了 handler 也就是得到了 phpredis,這樣就能夠隨便使用 phpredis原生的方法啦,並且仍是單例模式哦, 沒有新建對象額外的消耗
本文就到這裏結束啦, 若是要知道更多Cache類的使用方法,能夠按上文的方式直接看源代碼,或者再去查閱官方文檔.
雖然沒有講解如何使用,可是分析了 Cache的實現原理有助於提升咱們的編程抽象水平, 上文分析源碼的方式也一樣能夠用來分析其餘的核心類庫.