为何须要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的实现原理有助于提升咱们的编程抽象水平, 上文分析源码的方式也一样能够用来分析其余的核心类库.