Redis作为常用缓存,在工作中经常要使用到,对于一些常用又不变的数据,使用Redis可以缓解查询数据库的压力。相比Memcache只有键值对,Redis支持string,hash,list,set,zset五种基本数据类型。
一 安装扩展
php7环境,下载redis.so文件,新建30-redis.ini,输入extension=redis.so
打开虚拟机
ini文件放在/etc/php.d
so文件放在/usr/lib64/php/modules/
二 构建基类
对安装的Redis扩展进一步封装
<?php
namespace lib;
use RedisCluster;
/**
* Redis Cluster Lib
* 对phpRedis进行进一步封装,对于其原生函数可直接调用
* 但需要去除第一位的key,并通过keyConfig或者setKeyInfo进行设key
* 支持链式调用
*/
class RedisClusterLib {
/**
* redis集群机器配置文件路径及配置
* @var string
*/
protected $INI_PATH = '';
protected $INI_FILE = null;
protected $INI_CONFIG = null;
/**
* redis各KEY配置文件路径及配置
* @var string
*/
protected $KEY_PATH = '';
protected $KEY_FILE = null;
protected $KEY_CONFIG = null;
protected $MODULE = NULL;
/**
* key名各项分隔符,默认_
* @var string
*/
protected $KEY_DELIMITER = '_';
/**
* redisCluster实例
* @var RedisCluster
*/
protected $CLUSTER;
/**
* redisCluster名称,通过ini读取
* @var null
*/
private $CLUSTER_NAME = null;
/**
* RedisCluster constructor
* @param string $clusterName
* @param string $module
* @param int $mode
* 1 (default) 读主机,如果主挂了,可以读从机 2 随机主从 3 随机从机 4 只读主机
*/
public function __construct($clusterName = 'default', $module = '', $mode = 1, $configName = '') {
$this->INI_PATH = CONFIG_PATH . "/redis/{$clusterName}/redisCluster.ini.php";
$this->KEY_PATH = CONFIG_PATH . "/redis/{$clusterName}/redis.key.php";
$this->INI_FILE = include($this->INI_PATH);
$this->KEY_FILE = include($this->KEY_PATH);
$this->connect($clusterName, $module, $mode, $configName);
}
/**
* 主动连接
* @param $clusterName
* @param string $module
* @param int $mode
* @return $this
*/
public function connect($clusterName, $module = '', $mode = 1, $configName = '') {
$this->CLUSTER_NAME = trim($clusterName);
if (!empty($configName)) {
$this->CLUSTER_NAME = $configName;
}
$this->MODULE = empty($module) ? $this->CLUSTER_NAME : trim($module);
$this->INI_CONFIG = $this->INI_FILE[$this->CLUSTER_NAME];
try {
if (!empty($this->INI_CONFIG['nodes'])) {
$this->CLUSTER = new RedisCluster(null, $this->INI_CONFIG['nodes'], 1, null, false, $this->INI_CONFIG['auth'] ? $this->INI_CONFIG['auth'] : null);
$this->setMode($mode);
// 连接成功后设置key字典
$this->KEY_CONFIG = $this->KEY_FILE[$this->MODULE];
} else {
throw new \RedisClusterException('invalid redis cluster config', '010204');
}
} catch (\Throwable $e) {
if (strtolower(RUN_ENVIRONMENT) == 'dev') echo $e->getCode() . ':' . $e->getMessage() . "\n";
// cluster连接失败后记录日志
$this->Log([
'return' => $e->getMessage(),
'msg' => 'connect redis cluster failed'
]);
$this->setError($e->getCode());
}
return $this;
}
/**
* 异常记录日志
* @param $info
* @return bool
*/
private function Log($info) {
Log::error([
'type' => 'redis_cluster',
'call_file' => __FILE__,
'call_function' => __FUNCTION__,
'param' => [
'time' => date("Y-m-d H:i:s")
],
'return' => empty($info['return']) ? '' : $info['return'],
'msg' => empty($info['msg']) ? '' : $info['msg']
]);
return false;
}
/**
* 设置集群访问模式
* @param $mode
* @return $this
*/
public function setMode($mode) {
switch ($mode) {
case 2:
$this->CLUSTER->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_DISTRIBUTE);
break;
case 3:
$this->CLUSTER->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_DISTRIBUTE_SLAVES);
break;
case 4:
$this->CLUSTER->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_NONE);
break;
case 1:
default:
$this->CLUSTER->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_ERROR);
}
return $this;
}
/**
* 析构
*/
public function __destruct() {
// 断开连接
if (is_object($this->CLUSTER)) {
$this->CLUSTER->close();
}
}
/**
* 用于暂时存贮key相关信息
* @var
*/
private $keyInfo;
/**
* 如果需要可以读key配置生产key名,设置完后可直接链式操作
* @param $keyConfigName
* @param $postfixValue
* @return $this
*/
public function keyConfig($keyConfigName, $postfixValue = []) {
try {
if (!empty($this->KEY_CONFIG) && isset($this->KEY_CONFIG[$keyConfigName])) {
$this->keyInfo = $this->KEY_CONFIG[$keyConfigName];
// 设置后缀
if (!empty($postfixValue)) {
$tmpConfigPostfix = '';
$tmpArrayPostfix = '';
// 按顺序读取配置里的postfix
foreach ($this->keyInfo['postfix'] as $defaultKey) {
if (key_exists($defaultKey, $postfixValue)) {
if (is_array($postfixValue[$defaultKey])) {//支持mget
$tmpArrayPostfix = $defaultKey;
continue;
}
$tmpConfigPostfix .= $this->KEY_DELIMITER . $defaultKey . $this->KEY_DELIMITER . $postfixValue[$defaultKey];
} else {
throw new \RedisClusterException('invalid postfix value', '010205');
}
}
}
// 设置key,有后缀的追加在后面,没后缀的直接用key
$this->keyInfo['key'] .= empty($tmpConfigPostfix) ? '' : $tmpConfigPostfix;
if (!empty($tmpArrayPostfix)) {
$tmpKeyInfo = [];
foreach ($postfixValue[$tmpArrayPostfix] as $sKey) {
$tmpKeyInfo[] = $this->keyInfo['key'] . $this->KEY_DELIMITER . $tmpArrayPostfix . $this->KEY_DELIMITER . $sKey;
}
$this->keyInfo['key'] = $tmpKeyInfo;
}
} else {
throw new \RedisClusterException('invalid key config or key name', '010203');
}
} catch (\RedisClusterException $e) {
if (strtolower(RUN_ENVIRONMENT) == 'dev') echo $e->getCode() . ':' . $e->getMessage() . "\n";
// key配置不正确
$this->Log([
'return' => $e,
'msg' => 'set key config failed'
]);
$this->setError($e->getCode());
}
return $this;
}
/**
* 设置临时key配置,设置完后可直接链式操作
* @param $keyName
* @param null $ttl
* @return $this
*/
public function setKeyInfo($keyName, $ttl = null) {
$this->keyInfo = [
'key' => $keyName,
'ttl' => $ttl,
];
return $this;
}
/**
* 魔术方法
* 实际调用redisCluster的各方法
* @param $function
* @param $args
* @return $this
*/
public function __call($function, $args) {
// 链式操作时,如果有错直接返回error
if (!empty($this->error)) return $this;
try {
if (is_object($this->CLUSTER)) {
// key未设置直接报错
if (empty($this->keyInfo['key'])) throw new \RedisClusterException('invalid key', '010203');
// 第一个参数插入key
array_unshift($args, $this->keyInfo['key']);
$function = ltrim($function, '_');
$result = call_user_func_array([$this->CLUSTER, $function], $args);
$this->setResult($result);
} else {
throw new \RedisClusterException('invalid redis cluster', '010001');
}
} catch (\RedisClusterException $e) {
if (strtolower(RUN_ENVIRONMENT) == 'dev') echo $e->getCode() . ':' . $e->getMessage() . "\n";
// cluster操作失败后记录日志
$this->Log([
'return' => $e,
'msg' => 'call redis cluster function failed'
]);
$this->setError($e->getCode());
}
return $this;
}
/**
* 设置字符串型值
* @param string $value 值
* @param null $timeout 过期时间,支持formatTTL转换
* 默认null 读取配置内ttl
* -1 永不过期
* @return $this
*/
public function set($value, $timeout = null) {
// 默认取配置
if (!isset($timeout) && !empty($this->keyInfo)) $timeout = $this->keyInfo['ttl'];
// 解析格式
$timeout = $this->formatTTL($timeout);
$params = (!isset($timeout) || $timeout <= 0) ? [] : ['ex' => $timeout];
return $this->_set($value, $params);
}
/**
* 如果key不存在就设置一个值,能确保原子性
* @param $value
* @param $timeout
*/
public function setIfNotExist($value, $timeout = null) {
// 默认取配置
if (!isset($timeout) && !empty($this->keyInfo)) $timeout = $this->keyInfo['ttl'];
// 解析格式
$timeout = $this->formatTTL($timeout);
$params = ['nx'];
if ($timeout > 0) $params['ex'] = $timeout;
return $this->_set($value, $params);
}
/**
* 设置过期时间,可用于刷新
* @param $timeout -过期时间, 支持formatTTL转换
* 默认null 读取配置内ttl -1 永不过期
* @return $this
*/
public function expire($timeout = null) {
// 默认取配置
if (!isset($timeout) && !empty($this->keyInfo)) $timeout = $this->keyInfo['ttl'];
// 取完配置仍为null,给-1
if (!isset($timeout)) $timeout = -1;
// 解析格式
$timeout = $this->formatTTL($timeout);
// 如果为-1,调用persist,去除过期时间,正常数字调用expire
return $timeout === -1 ? $this->_persist() : $this->_expire($timeout);
}
/**
* @brief 在redis中查找键值是否存在
* @note 在redis中查找键值是否存在
*/
public function exist() {
$bRet = false;
$aResult = $this->exists()->end();
if ($aResult['status'] == 1 && ($aResult['result'] == 1 || $aResult['result'] === true)) {
$bRet = true;
}
return $bRet;
}
/**
* @brief 查看某一元素是否存在集合中
* @param string $value 元素值
*/
public function sIsMemberU($value) {
$bRet = false;
$aResult = $this->_sismember($value)->end();
if ($aResult['status'] == 1 && ($aResult['result'] == 1 || $aResult['result'] === true)) {
$bRet = true;
}
return $bRet;
}
/**
* TTL时间转换函数
* @param $type
* -1 永不过期
* 纯数字 过期秒数
* today 此刻至23:59:59
* 24h 24小时整
* @return false|float|int|null
*/
private function formatTTL($type) {
$ttl = null;
switch ($type) {
case null:
break;
case -1:
$ttl = -1;
break;
case is_numeric($type):
$ttl = intval($type);
break;
case 'today':
$ttl = strtotime(date("Y-m-d", strtotime("+1 day"))) - time();
break;
case '24h':
$ttl = 3600 * 24;
break;
case '1h':
$ttl = 3600;
break;
}
return $ttl;
}
/**
* 结束函数
* 如果有错返回error,没有错返回result
* @return array
*/
public function end() {
return empty($this->error) ? $this->result : $this->error;
}
/**
* 返回结果
* @var null
*/
public $result = null;
/**
* 设置返回结果
* @param $result
* @return array|null
*/
private function setResult($result) {
return $this->result = ['status' => 1, 'result' => $result];
}
/**
* 增加错误属性,用于返回错误信息
* @var null
*/
public $error = null;
/**
* 发生错误时,设置错误CODE
* @param $errorCode
* @return array
*/
private function setError($errorCode) {
if (empty($errorCode)) $errorCode = '999999';
$this->result = null;
$errorCode = str_pad($errorCode, 6, 0, STR_PAD_LEFT);
return $this->error = ['status' => 0, 'error_code' => $errorCode];
}
public function getRedisClusterInstance() {
return $this->CLUSTER;
}
}
三 服务器节点的配置
开发环境两台服务器,六个节点。
return [
'base' => [
'auth' => '123456',
'nodes' => [
'10.100.6.114:7000',
'10.100.6.114:7001',
'10.100.6.114:7002',
'10.100.6.115:7000',
'10.100.6.115:7001',
'10.100.6.115:7002',
]
]
];
四 模块的分类
return [
'company' => [ //模块名
'basecompany' => [
'key' => 'BASECOMPANY_V1', //前缀
'postfix' => [
'coid' //字段
],
'ttl' => 86400, //有效期
],
],
];
五 使用方式
初始化
$this->redis = new RedisClusterLib('base', 'company');
设置数据
$this->redis->keyConfig('basecompany', ['coid' => '123456'])->set($data)->end();
获取数据
$this->redis->keyConfig('basecompany', ['coid' => '123456'])->get()->end();
清除数据
$this->redis->keyConfig('basecompany', ['coid' => '123456'])->del()->end();
比如限制用户每天登录输错10次,可以定义一个key,每次让其自增。
//首次
$term = strtotime(date("Y-m-d") . " 23:59:59") - time(); //当天剩余时间
$term = $term <= 0 ? 1 : $term; //最小ttl
$this->redis->keyConfig('login', ['hruid' => '123456'])->incr()->expire($term)->end();
//其他次
$this->redis->keyConfig('login', ['hruid' => '123456'])->incr()->end();
以上就是Redis的使用总结。