• 技术文章 >后端开发 >php教程

    TP5之Cache的原理及使用

    *文*文2018-05-11 15:09:17原创21021
    在当今大流量的互联网之中,Cache的重要性不言而喻。ThinkPhp5作为国内主流框架,提供了强大的Cache功能。让我们跟随本文,来剖析TP5 Cache的原理及使用。

    为什么需要Cache(缓存)?

    假设现在有一个小说网,有非常多的读者,有一篇新的章节更新了,那么可能一分钟内有几万几十万的访问量.

    如果没有缓存,同样的内容就要去数据库重复查询,那可能网站一下就挂掉了.

    追求性能的web站点应该充分利用缓存,常见的缓存类型有File,Memcache,Redis等,这里就不说他们的区别了

    今天我们分析下TP5 Cache的内部实现原理.

    首先看官方文档如何使用缓存的.

    1.png

    如上图,调用Cache类的的静态方法set就可以直接使用了,我们查看Cache类文件 在application/thinkphp/library/think目录下

       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方法

    我们再看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就是函数传入的参数,也就是配置信息,我们看下配置信息
    2.pngtype就是驱动方式,如果我们type填写的是File,那么就使用文件驱动,实例化的是 \think\cache\driver\File.class
    我们看下 \think\cache\driver文件下有什么文件,那就知道thinkphp为我们提供了多少种缓存驱动了

    3.png

    原来有这么多!

    点进去

    每个文件,我们可以发现一个共同点, 每个类都是继承了 抽象类 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类

    4.png

    可以看到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的实现原理有助于提高我们的编程抽象水平, 上文分析源码的方式也同样可以用来分析其他的核心类库.

    相关阅读:

    ThinkPHP5商城项目实战视频教程课件源码分享

    thinkphp5入门该了解的知识

    thinkphp5.0学习笔记之数据库的操作

    以上就是TP5之Cache的原理及使用的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:TP5 Cache php
    上一篇:PHP-TSRM线程安全管理器-源码分析 下一篇:Apache和Nginx如何选择
    20期PHP线上班

    相关文章推荐

    精选22门好课,价值3725元,开通VIP免费学习!• 细思极恐?关于in_array的第3个参数• PHP 和 XML: 使用expat函数(三)• CodeIgniter中使用cookie的三种方式详解_php实例• php去除HTML标签实例_php实例• 基于在生产环境中使用php性能测试工具xhprof的详解_php实例
    1/1

    PHP中文网