前言
这篇博客主要讲实现一个缓存类的思路。需要提前知道这些知识点:类的自动加载、php对文件的操作、php中的几种设计模式、redis基础知识、memcache和memcached基础知识。后面我会补充上传所有文件的源代码。
知识储备
设计模式
主要用到以下几个设计模式:
- 工厂模式
- 单例模式
- 注册器模式
- 适配器模式
文件操作
- 读文件
- 写文件
类的自动加载
- 魔术方法:__autoload
- spl_autoload_register
缓存nosql
- redis
- memcache
- memcached
实现思路
- 首先实现一个自动加载类,用来实现类的自动加载。
- 然后定义一个抽象类,定义缓存类通用功能的统一规范。
- 再然后,分别实现不同的缓存类,如memcache、redis等,继承上边的抽象类,实现规定好的抽象方法。
- 最后实现一个缓存调用的入口类。需要实现将传入的缓存类型(如redis)初始化。
整体结构
- Cache文件夹里面存储所有具体实现缓存类以及缓存抽象类。
- Cache.php是缓存调用入口类。
- Register.php里面实现了类的自动加载。
- index.php是业务调用缓存的代码。
具体实现
这里主要讲一下自动加载的实现,抽象类的实现,以及缓存调用入口类文件的实现。具体实现的缓存类,我们就用redis来做一个例子。
自动加载
自动加载主要用到spl_autoload_register函数,spl_autoload_register() 满足了多条 autoload 的需求。 它实际上创建了 autoload 函数的队列。
主要实现代码(忽略了部分代码):
Register.php:
public function __construct() {
spl_autoload_register(array($this, 'autoload'));
}
public static function autoload($name)
{
if (trim($name) == '') {
new \Exception('No class or interface named for loading');
}
// 规范类名称
$name = self::formatClassName($name);
if (isset(self::$_loadedClass[$name])) {
self::$_loadedClass[$name]++;
return;
}
$file = __DIR__ . DIRECTORY_SEPARATOR . strtr($name, ['_' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR]) . '.php';
if (!$file || !is_file($file)) {
return;
}
// 导入文件
include $file;
if (!self::classExists($name, false) && !self::interfaceExists($name, false)) {
throw new \Exception('Class or interface does not exist in loaded file');
}
if (empty(self::$_loadedClass[$name])) {
self::$_loadedClass[$name] = 1;
}
}
抽象类
因为是缓存类,所以必须定义缓存初始化、缓存值的设置、缓存值的获取、缓存值的删除、清空所有缓存等功能。
实现主要代码(删除了部分代码):
AbstractCache.php:
<?php
/**
* 缓存抽象类
* Class AbstractCache
*/
abstract class AbstractCache
{
/**
* 主服务器
*/
protected $_masterObj;
/**
* 从服务器
*/
protected $_slaveObj;
/**
* 初始化
*
* @param $conf
* @return $this
*/
abstract public function init($conf);
/**
* 获取数据
*
* @param array|string $key
* @param bool $checkClear 检查是否被清除
* @param int $time 以引用的方式返回数据添加时间
* @param int $expire 以引用的方式返回当前值的过期时间
* @return mixed
*/
abstract public function get($key, $checkClear = true, &$time = null, &$expire = null);
/**
* 写入数据
*
* @param $key
* @param null $value
* @param int $expire
* @return mixed
*/
abstract public function set($key, $value = null, $expire = 3600);
/**
* 删除数据
*
* @param $key
* @return mixed
*/
abstract public function delete($key);
/**
* 删除所有缓存
* @return bool
*/
abstract public function flush();
/**
* 生成KEY值
*
* @param array $param
* @return string
*/
public function makeKey($param = null)
{
if (empty($param)) {
return '';
}
if (is_array($param)) {
ksort($param);
$param = http_build_query($param);
}
return md5($this->getModuleName() . $param);
}
/**
* 包装VALUE
*
* @param mixed $val
* @param int $expire
* @return array
*/
final protected function _makeValue($val, $expire = 3600)
{
$val = array(
'value' => $val,
'expire' => $expire,
'time' => $_SERVER['REQUEST_TIME'] ?? time(),
);
return $val;
}
/**
* 获取原始的缓存对象
*
* @param bool $master
* @return mixed
*/
final public function getOriginObj($master = true)
{
return $master ? $this->_masterObj : $this->_slaveObj;
}
}
redis的缓存
这里就只用redis做一个具体实现。主要实现抽象类中的抽象方法。
具体实现类(删除了部分代码)
Redis.php:
<?php
class RedisCache extends AbstractCache
{
const CONNECT_TYPE_MASTER = 'master';
const CONNECT_TYPE_SLAVE = 'slave';
/**
* @var Redis
*/
protected $_masterObj;
/**
* @var Redis
*/
protected $_slaveObj;
/**
* 初始化
*
* @param $conf
* @return $this
* @throws \Exception
*/
public function init($conf)
{
if (empty($conf['master'])) {
throw new \Exception('config is empty!');
}
if (!$this->_masterObj || !$this->_slaveObj) {
$_master = $conf['master'];
$_masterObj = self::_createConn($_master);
if (!empty($conf['slaves'])) {
$_slave = count($conf['slaves']) > 1 ? array_rand($conf['slaves']) : reset($conf['slaves']);
$_slaveObj = self::_createConn($_slave);
} else {
$_slaveObj = $_masterObj;
}
$this->_masterObj = $_masterObj;
$this->_slaveObj = $_slaveObj;
}
if (!empty($conf['options'])) {
$this->_setOptions($conf['options']);
}
return $this;
}
/**
* 清除所有数据
*
* @return bool
*/
public function flush()
{
return $this->_masterObj->flushDB();
}
/**
* 获取数据
*
* @param array|string $key
* @param bool $checkClear 检查是否被清除
* @param int $time 以引用的方式返回数据添加时间
* @param int $expire 以引用的方式返回当前值的过期时间
* @return mixed
*/
public function get($key, $checkClear = true, &$time = null, &$expire = null)
{
$_key = $this->makeKey($key);
$res = $this->_slaveObj->get($_key);
$res = unserialize($res);
if ($res && isset($res['value'])) {
$time = $res['time'];
$expire = $res['expire'];
if ($checkClear) {
unset($res);
return null;
}
return $res['value'];
}
return null;
}
/**
* 写入数据
*
* @param $key
* @param null $value
* @param int $expire
* @return mixed
*/
public function set($key, $value = null, $expire = 3600)
{
return $this->_masterObj->set($this->makeKey($key), serialize($this->_makeValue($value, $expire)), $expire);
}
/**
* 删除数据
*
* @param $key
* @return void
*/
public function delete($key)
{
$this->_masterObj->delete($this->makeKey($key));
}
/**
* 创建redis对象
*
* @param array $conf 配置 {host => string, port => int, timeout => float}
* @return Redis
*/
protected static function _createConn(array $conf)
{
$_obj = new Redis();
$_connectFunc = empty($conf['pconnect']) ? 'connect' : 'pconnect';
$_obj->$_connectFunc($conf['host'], $conf['port'], $conf['timeout']);
return $_obj;
}
protected function _setOptions(array $options)
{
foreach ($options as $_key => $_val) {
$this->_masterObj->setOption($_key, $_val);
$this->_slaveObj->setOption($_key, $_val);
}
}
}
缓存入口类
这里是所有缓存类的入口。我们调用缓存类,统一实例化这个类。
具体实现代码(删除了部分代码):
Cache.php:
/**
* 单例
*
* @param null $_type
* @return
* @throws \\Exception
*/
public static function instance($_type = null)
{
if (empty($_type)) {
self::getConfig('DEFAULT');
}
if (!$_type) {
throw new \Exception("The type can not be set to empty!");
}
if (!isset(self::$_instance[$_type])) {
$conf = self::getConfig($_type);
if (empty($conf)) {
throw new \Exception("The '{$_type}' type cache config does not exists!");
}
// 获取类名称
$class = self::getClassName($_type);
$obj = new $class();
if (!$obj instanceof AbstractCache) {
throw new \Exception("The '{$class}' not instanceof AbstractCache!");
}
$obj->init($conf);
self::$_instance[$type] = $obj;
} else {
$obj = self::$_instance[$type];
}
return $obj;
}
所有的实现部分到这里就结束了。
我们来使用一下:
$cacheObj = Cache::instance('memcache');
$cacheObj->set(array('id' => 2), array('name' => '浪子燕青'), 3600);//添加数据
$cacheObj->get(array('id' => 2));//获取数据
我们可以用这种方式实现更多的功能,比如:
- 数据库的操作,可以实现多种数据库的CRUD
- 消息队列的实现,可以实现各种工具消息队列的入队、出队。