ThinkPhp5源碼剖析之Cache

爲何須要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的實現原理有助於提升咱們的編程抽象水平, 上文分析源碼的方式也一樣能夠用來分析其餘的核心類庫.

相關文章
相關標籤/搜索