前方高能,前排预警,大量图文!!! 手机看帖的小伙子请注意你的流量套餐!!!
好了,切入正题。
在关系型数据库一统天下的时候,有一个意大利人开发出了一种非关系型数据库 Redis,用来解决关系型数据库在面对大流量高并发产生的数据库巨大读写压力,Redis以高效的读写效率闻名,比起memecache, Redis拥有更多的数据类型,在面对复杂的应用场景时,有着更多更合适的类型选择。所以为什么我们要用Redis?因为传统的结构严谨的关系型数据库,其劣势是在大量读写压力之下,性能下降得非常明显,这个时候项目的用户体验就会明显下降,我们不得不引入Redis来充当程序跟数据库之间的一个桥梁,帮助缓解关系型数据库的压力,但是Redis并不能取代传统的关系型数据库(比如mysql) ,因为大量的应用数据需要持久化写入硬盘,而Redis的持久化并不十分理想(下面会给大家介绍),所以Redis起的是一个辅助作用。
首先回顾下Redis的基础知识( 我们这里不讲拗口难懂的名词 )
1 什么是Redis?
答:一种nosql数据库(非关系形数据库)。
2 为什么要用Redis?
答:缓解数据库压力
3 Redis有哪几种数据类型?
1 字符串(string) 2 哈希( hash ) 3 队列(list) 4 集合(set) 5 有序集合(sorted set)
如果对于Redis数据类型不太了解的可以自行百度,本章不作科普,显得太啰嗦。
知识这个东西,除了了解之外,必须加以实践对不对?马上给大家奉上干货,由于之前的开发的源码找不到了,在下只好重新简单地写了几个demo ,旨在传达思想,不作为具体的应用最佳实现,以下基于PHP向大家讲解。
注意:Redis所有的数据类型的存储都是基于key=>value的方式来保存的
字符串(string)
最简单,应用最多的一个数据类型。
比如我们网站有一个需求是要求用户请求短信接口,我们需要判断用户在一分钟内不能再次发起请求,必须至少等待一分钟。
那么剖析一下这个需求,2个点
1:一个用户(其实就是手机号)
2:单位时间 (可以想到就是过期时间)
因为Redis的所有键都是可以设置过期时间的,那么解决思路是把请求的用户手机号+1分钟的存储时间
模拟代码:
<?php
include_once("./RedisService.class.php");
$redis = new RedisService();
$mobile='137****7242';//任意一个手机号
$expire=60;
$keyName="message:".$mobile;
if($redis->get($keyName))
{
die("一分钟内不能频繁请求");
}
$re=$redis->set($keyName,1,$expire);
if($re)
{
echo '发送成功';
}
那么在第一次运行此代码的时候,如下:
由于Redis中并没有该用户的数据,所以会往Redis中写入该用户的信息
我们定义键名的时候要以:来间隔,这样可以生成如图所示的层级目录,方便查看管理。
可以看到我们定义的键名是message:137****7242 ,键值是1 ,TTL是过期时间,截图的时候已经不足一分钟
那么我们再次运行此代码可以看到:
因为Redis中已经存在该用户的发送信息了,那么在一分钟内再次请求都会被拦截下来
一分钟后可以看到redis中的message:137****7242这个键已经不见了(过期清理):
这个时候我们再次请求:
可以发现又请求成功了
这也就实现了对用户单位时间内请求次数的限制,结合实际项目可以做一些相应的扩展。
string类型最经常的情况下的是拿来做缓存的。
像一些高频访问的数据,但是又不是更新很频繁的,就适合缓存起来。
比如首页有个热门菜谱这么一个列表,显示10条菜谱,那么这个就是热点数据,因为访问频率是最高的,如果采用去mysql中查询的方式,那么在菜谱表数据量一上来,过了百万级,查询效率就下降比较明显了。另外一个是日后用户量一增加,数据库的吞吐量不足的话,也会使得查询效率明显降低,那么我们可以选择把这部分热点数据给缓存到Redis中去,之后的所有数据读取都不经过数据库,都会到Redis中去读取数据
模拟代码:
<?php
include_once("./RedisService.class.php");
include_once("./Mysql.class.php");
$db = new Mysql();
$redis = new RedisService();
$keyName="dishes:hot:top10";
//如果有缓存
if($hotDishes=unserialize($redis->get($keyName)))
{
echo '从缓存中获得这些数据:<br>';
var_dump($hotDishes);
}
else
{
//没有缓存,查询数据库
$sql="select dishes_id,dishes_name,dishes_image from dishes where is_hot =1 order by dishes_id DESC limit 10";
$hotDishes=$db->query($sql);
if(!empty($hotDishes))
{
//写入Redis
$redis->set($keyName,serialize($hotDishes),3600*2);
}
var_dump($hotDishes);
}
那么我们来看看效果先 ,一会再讲解
第一次运行此代码的时候可以看到查出来10条数据:
以及在Redis中生成了缓存:
缓存的时间TTL是我们设置的两个小时,注意因为Redis的string类型只能存储字符串,所以我们的数组要经过serialize()序列化之后才可以存储,同理取出的时候也要反序列化(见代码)。那么在两个小时之内我们都可以通过读取缓存来获取这个数据,再次访问:
这就是string 类型用于数据缓存的基本用法。
哈希类型(hash)
哈希类型最典型的一个应用,就是作为Session的替换方案,在单机服务器的时候我们可能没有感觉到Session的潜在问题,可是如果日后业务发展加快,需要水平扩展服务器的时候(负载均衡),Session共享就会成为一个问题,虽说nginx有根据IP分发请求的策略,但不是最优解,我们最理想的是可以实现所有Session的集中管理。
这里我们只做最最简单的演示 , 在实际的登陆中还有非常多的验证:
<?php
include_once("./RedisService.class.php");
include_once("./Mysql.class.php");
$db = new Mysql();
$redis = new RedisService();
$username=$_POST['username'];
$password=$_POST['password'];
$sql="select * from pr_user where user = '$username'";
$re=$db->query($sql);
if($re[0]['pass']==md5($password.'zc'))
{
//生成SESSION
$session_id=getRandomString(20);
$session_redis_key="session:".date("Y-m-d",time()).":".$session_id;
unset($re[0]['pass']);
//写入Redis
$redis->hashSet($session_redis_key,$re[0],3600*2);
//写入数据库
$sql="update pr_user set session_id = '$session_id'";
$setRe=$db->query($sql);
if($setRe)
{
//设置cookie
setCookie("session_id",$session_id);
echo 'login successfully';
}
}
function getRandomString($len, $chars=null)
{
if (is_null($chars)){
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
}
mt_srand(10000000*(double)microtime());
for ($i = 0, $str = '', $lc = strlen($chars)-1; $i < $len; $i++){
$str .= $chars[mt_rand(0, $lc)];
}
return $str;
}
我们要做的就是模拟session_id 用20位的随机数来代表session_id,Redis键名用 'session:日期:session_id' 这样的形式来保存,保存的值是用户的基本信息,然后向客户端发起设置Cookie的请求,setCookie("session_id",$session_id),具体的登陆页面请各位自行创建。
那么我们在登陆页面把用户名密码输入之后,提交到这个文件会得到:
可以看到登陆成功了,这个时候我们看一下数据库:
我们已经把session_id 保存到数据库了,保存下来的原因是方便后期对用户的追踪。
我们再看下Redis:
可以看到Redis中也生成了对应的哈希值,过期时间2小时。
那么设置好了要怎么用呢?假设我用户中心的代码如下:
<?php
include_once("./RedisService.class.php");
include_once("./Mysql.class.php");
$db = new Mysql();
$redis = new RedisService();
$session_id = $_COOKIE['session_id'];
$session_redis_key="session:".date("Y-m-d",time()).":".$session_id;
$session = $redis->hashGet($session_redis_key);
if(!$session)
{
header("location:./1.html");
}
$_SESSION=$session;
echo '您已经登陆了,您的信息是:<br>';
var_dump($_SESSION);
首先获取客户端传过来的Cookie.session_id ,比如 5LJyeAgA9LpZSQeJenBC
然后拼接成 session:2018-08-30:5LJyeAgA9LpZSQeJenBC,读取Redis中对应的hash值,不同于string类型的是,hash值取出来的时候就是一个关联数组了,不需要反序列化。最终我们取得的数据是:
将这个数组赋值给$_SESSION超全局变量就可以在任何一处调用了,结合实际项目的话建议将这一层放在顶层的基类,这样就基本完成了Redis代替SESSION的解决方案,好处是做水平扩展的时候只需要配置几台机器的Redis,就可以共用同一个Redis数据库
队列(List)
队列算是Redis的一大核心特色吧,在短时间内流量聚集非常集中的时候(比如秒杀、抢购活动), 数据的入库往往是个问题,短时间的数据几种入库分分钟让数据库宕机,结合Redis的队列,我们可以解决两个问题:
1 产品超卖
2 数据库宕机
先从产品超卖要怎么防止这一点来讨论
如果说我用这样的代码来控制产品库存
<?php
$store_all=10;//库存最大数量
$sell_num=8;
$store_num=$store_all-$sell_num;//10-8=2
if($store_num>0)
{
//创建订单代码
//减少库存代码
}
$store_num表示剩余库存,咋一看 if($store_num>0) 就给用户下订单,貌似没有问题。
但是!如果是在高并发的场景下,当有三个人同时购买,经过if($store_num>0)这一步的时候都是有库存的,但是最后却三个人都下了订单,也就是超卖了
那这个怎么解决呢?有没有什么方法让判断库存的时候不要同时进行判断呢?可不可以一个一个来?答案是可以的!
Redis的队列 可以让多个进程基于其原子性实现单一进程,一个一个过,一个一个来。
思路:比如我某个产品进行抢购,这个产品ID为1 库存为10 那就创建一个长度为10的队列,每个用户抢购的时候就弹出一个,弹完了库存也就没了,没库存了就下不了订单。
奉上示例:
抢购前,后台先创建库存队列(预热):
<?php
include_once("./RedisService.class.php");
$redis = new RedisService();
//10个库存
$store_all=10;
//商品ID为1
$goods_id=1;
$keyName="store:id:".$goods_id;
$element=1;//无实际意义
for($i=1;$i<=$store_all;$i++)
{
$redis->push($keyName,$element);
}
echo 'finish push !';
运行:
对应Redis中也生成了一个队列:
那么接下来就是用户在购买下单的时候,每次从这个队中弹出一个,没有元素可以弹出了就表示卖完了,弹出成功的话就创建另外一个order队列,往队列中push一个订单,先不写入数据库,这个时候写入数据库压力太大了。
<?php
include_once("./RedisService.class.php");
$redis = new RedisService();
//商品ID=1的库存
$keyName="store:id:1";
$popResult=$redis->pop($keyName,'right');
//弹出成功,还有库存
if($popResult)
{
//创建订单写入订单信息
$orderInfo['create_time']=date("Y-m-d H:i:s",time());
$orderInfo['buyer_id']=rand(1000,9999);
$orderInfo['address_id']=rand(10000,99999);
//写入Redis队列
$keyName="order:goods_id:1";
$pushResult=$redis->push($keyName,serialize($orderInfo));
if($pushResult)
{
echo '抢购成功';
}
}
else
{
echo '抢购结束';
}
这里我运行了11次这个文件,模拟11个用户购买,因为这台电脑上没有并发测试工具,看官将就看,只是演示一个思路。
可以看到在第11次的时候就提示抢购结束了:
这个时候我们来看看Redis:
我们可以看到Redis中的库存列表(store:id:1)不见了!只有一个订单列表(order:goods_id:1)对应着之前抢购的10个产品
这是因为队列在弹出完了之后就从内存中释放了。没有了库存列表,自然无法继续抢购下去,这样就可以防止超卖。
在把订单存入到Redis中后,就可以先返回给用户抢购成功的提示了。具体的订单信息写入数据库,我们用异步的脚本来写入。
示例代码:
<?php
include_once("./RedisService.class.php");
include_once("./Mysql.class.php");
$redis = new RedisService();
//读取Redis订单列表
while(true)
{
$keyName = "order:goods_id:1";
//出队反序列化
$orderInfo = unserialize($redis->pop($keyName));
if($orderInfo)
{
//入库
$orderId="M192".rand(1000,9999).time().rand(100000,999999);
$db = new Mysql();
$sql="insert into pr_order (`id`,`order_id`,`buyer_id`,`goods_id`,`address_id`,`create_time`) values (default,'$orderId',$orderInfo[buyer_id],1,$orderInfo[address_id],'$orderInfo[create_time]')";
$re=$db->query($sql);
if($re)
{
echo '订单写入成功';
}
}
}
这里我们简单地用一个死循环来不停读取Redis中的订单队列,每次订单队列弹出一个元素,把这个元素的信息组织好后写入数据库,就是这么简单。
在我们运行这个脚本之后,可以看到订单都被写入数据库了:
这样可以防止对数据库的冲击太大,防止宕机。
综上就是提供这么一个思路,利用队列来对瞬间并发做这么一个处理,可以有效防止超卖,将用户的抢购流程和系统的订单创建给分开了,用户先抢购完了,我系统再慢慢地创建订单。
队列还有其他一些应用场景,大家在运用的时候多加思考。
集合(set)
集合相信大家都不会陌生了,什么交集、并集、差集,都是这些基本原理的应用。
大家使用集合的时候心里要有一个概念叫做 ‘范围’。
比如说你微博关注的人,我微博关注的人,现在求我们俩共同的关注好友,怎么求?
这个时候可以理一下思路,把问题转化成,求你跟我关注的好友”范围“的交集。
那不就是两个集合的交集?
这个具体的应用不是特别多,这里这是简单介绍一下思路,具体应用的时候多想想集合的交集、并集、差集就可以了。
利用集合中的元素都是唯一的不重复的,像一些活动每人每天只能参加一次之类的,把参加过活动的用户扔到一个集合里,今天参加过这个活动没有就只需要看这个用户在不在这个集合里就可以知道了把,activity:10 (参加过活动ID为10的用户的ID的集合) ,我这么说大家能理解吧。
想这样就可以表示活动id为10的参加用户有 11、33、36
有序集合(sorted set)
这个用得可能要比集合(set)要多一点,它的好处在于集合中每一个成员都有一个分数(score)
像这样:
那么我们可以想到,分数是不是有高低啊?分数是不是可以排序啊?这个就类似排行榜之类的
比如一场考试,可以把每个人的成绩都放进去,最后找出来前10名的学生可以吧?
同样是考试,考3科,语文数学英语,那就可以三个科目各自一个集合,然后求学生的总成绩只要拿这三个集合做一个交集然后存到另外一个新的集合里,这个新的集合的成员是之前集合的成员交集,分数是之前集合交集成员的分数总和,这不就是一个三科的总分?
这里我就不贴代码了。在尝试应用的时候只要把思路理清了并不难。
可以看到Redis的五种基础数据类型各有特色,可以轻松使用于不同的场景需求,比起单一的string类型的Memecache ,Redis必然成为大热之选。
好了,今天就先讨论到这里,后续会将更多的技术分享给大家。
如果你觉得还不错的话,可以转个贴分享给小伙伴,但是拒绝复制删帖,还是应该支持原创的。-.-
上次问我要Redis封装类库的小伙伴这里说一下,我的类库是自己封装的,因为我不太喜欢扩展里边的方法命名跟参数顺序,所以就自己封装了一个,符合我自己的使用习惯,不知道你们喜不喜欢,先贴上吧。
<?php
class RedisService
{
//默认配置参数,根据项目自行读取配置值
private static $_connectInfo = array(
'host'=>'127.0.0.1',
'port'=>6379,
'auth'=>'123123'
);
//报错设置,根据项目读取配置值
private static $debug = true;
//redis对象的存储变量
private static $redisObj;
/**
* 实例化自动连接Redis服务
* @param array $connectInfo [自定义连接参数数组]
*/
public function __construct($connectInfo=array())
{
if(!empty($connectInfo))
{
self::$_connectInfo = $connectInfo;
}
self::RedisConnect();
}
/**
* Redis连接方法
*/
private static function RedisConnect()
{
$redis = new Redis();
$connectResult=$redis->connect(self::$_connectInfo['host'],self::$_connectInfo['port']);
if(!$connectResult)
{
self::error('Redis connect fail,please check the configure');
}
$authVerify=$redis->auth(self::$_connectInfo['auth']);
if(!$authVerify)
{
self::error('The wrong auth ');
}
self::$redisObj = $redis;
}
/**
* [rangeToArray 工具函数:范围字符串转换成数组]
* @param [string] $range [范围字符串]
* @return [array] [范围数组]
*/
public function rangeToArray($range)
{
if(!substr_count($range,'->')||substr_count($range,'->')>1||strpos($range,'->') ==strlen($range)-1)
{
self::error("The range need to like a->b");
}
$rangeArr = explode('->',$range);
return $rangeArr;
}
/**
* [is_array 是否为数组检查方法]
* @param [array] $var [检查对象]
* @param [string] $methodName [请求方法]
* @return [boolean] [检查结果]
*/
private function arrayCheck($var,$methodName)
{
if(!is_array($var))
{
self::error("function $methodName : the param must be an array");
}
}
/**
* [stringCheck 是否为字符串检查方法]
* @param [number] $var [检查对象]
* @param [string] $methodName [请求方法]
* @return [boolean] [检查结果]
*/
private function stringCheck($var,$methodName)
{
if(!is_string($var))
{
self::error("function $methodName : the param must be string type ");
}
}
/**
* [numberCheck description]
* @param [number] $var [检查对象]
* @param [string] $methodName [请求方法]
* @return [boolean] [检查结果]
*/
private static function numberCheck($var,$methodName)
{
$preg="/^[0-9]+([.]{1}[0-9]+){0,1}$/";
$matchResult=preg_match($preg,$var);
if(!$matchResult)
{
self::error("function $methodName: the value for current key is not a number");
}
}
/**
* [error 报错方法]
* @param [string] $msg [报错信息]
*/
private static function error($msg)
{
if(self::$debug)
{
echo $msg;
}
die();
}
/**
* [set 字符串类型 key->value设置]
* @param [string] $key [键名]
* @param [string] $value [键值]
* @param [int] $expire [过期时间]
*
*/
public function set($key,$value,$expire=null)
{
if(!isset($key)||!isset($value))
{
self::error('function set lack of params');
}
self::stringCheck($key,__FUNCTION__)&&self::stringCheck($value,__FUNCTION__);
if(!$expire)
{
return self::$redisObj->set($key,$value);
}
else
{
return self::$redisObj->setex($key,$expire,$value);
}
}
/**
* [get 字符串类型 key->value获取]
* @param [string] $key [键名]
* @return [string] [返回值]
*/
public function get($key)
{
return self::$redisObj->get($key);
}
/**
* [mset 字符串类型 同时设置多个]
* @param [array] $data [键值对数组]
* @return [boolean] [返回值]
*/
public function mset($data)
{
self::arrayCheck($data,__FUNCTION__);
foreach($data as $k => $v)
{
$childResult = $this->set($k,$v);
if(!$childResult)
{
self::error($k."->".$v."write fail,the program ends");
}
}
return true;
}
/**
* [mget 字符串类型根据多个键名获取值]
* @param [array] $keys [key数组]
* @return [array] [key=>value数组]
*/
public function mget($keys)
{
self::arrayCheck($keys,__FUNCTION__);
$return=array();
foreach($keys as $key)
{
if(!is_string($key))
{
self::error("function ".__FUNCTION__.": elements all should be string type");
}
$value= $this->get($key);
if($value)
{
$return[$key] = $value;
}
}
return $return;
}
/**
* [delete 删除键统一操作函数]
* @param [string] $key [键名]
* @return [int] [受影响行]
*/
public function deleteKey($key)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->delete($key);
}
/**
* [alterNumber 字符串类型修改数字键值]
* @param [string] $key [键名]
* @param [int] $range [修改范围]
* @return [boolean] [是否修改成功]
*/
public function alterNumber($key,$range=0)
{
self::stringCheck($key,__FUNCTION__);
//变更范围不为0
if(!$range)
{
self::error("function ".__FUNCTION__.": the changed range can not be 0 ");
}
$value=$this->get($key);
if($value===false)
{
self::error("function ".__FUNCTION__.":the value for key ".$key." is no exist");
}
//是不是数字类型
self::numberCheck(abs($value),__FUNCTION__);
//统一使用incrByFloat
$result=self::$redisObj->incrByFloat($key,$range);
return boolval($result);
}
/**
* [hashSet 哈希类型设置函数]
* @param [string] $key [键名]
* @param [array] $array [hash array]
* @param [int] $expire [过期时间,不传之默认永久]
* @return [boolean] [是否成功]
*/
public function hashSet($key,$array,$expire=null)
{
self::stringCheck($key,__FUNCTION__);
if(count($array)>=1)
{
$flag=true;
$keySet=self::$redisObj->hmset($key,$array);
if($expire)
{
$flag=self::$redisObj->setTimeOut($key,$expire);
}
return $keySet&&$flag;
}
}
/**
* [hashGet 获取哈希对象函数]
* @param [type] $key [键名]
* @param array $fieldArray [field数组 不传值获取全部]
* @return [type] [结果数组]
*/
public function hashGet($key,$fieldArray=array())
{
self::stringCheck($key,__FUNCTION__);
if(empty($fieldArray))
{
return self::$redisObj->hGetAll($key);
}
else
{
if(is_array($fieldArray)&&!empty($fieldArray))
{
return self::$redisObj->hmget($key,$fieldArray);
}
}
}
/**
* [hashAlterNumber hash数字类型修改函数]
* @param [string] $key [键名]
* @param [string] $field [修改字段]
* @param integer/float $range [修改范围]
* @return [boolean] [是否修改成功]
*/
public function hashAlterNumber($key,$field,$range=0)
{
//变更范围不为0
if(!$range)
{
self::error("function ".__FUNCTION__.": the changed range can not be 0 ");
}
$valueArray=$this->hashGet($key,array($field));
//是否有值
if($valueArray[$field]===false)
{
self::error("function ".__FUNCTION__.":the field <b>".$field."</b> is no exist in hash key <b>".$key."</b>");
}
//值是不是数字类型
self::numberCheck(abs($valueArray[$field]),__FUNCTION__);
//统一使用hIncrByFloat
$result=self::$redisObj->hIncrByFloat($key,$field,$range);
return boolval($result);
}
/**
* [RemoveHashField 哈希类型移除字段函数]
* @param [string] $key [键名]
* @param [string] $field [字段名]
* @return [boolean] [是否移除成功]
*/
public function RemoveHashField($key,$field)
{
$valueArray=$this->hashGet($key,array($field));
//是否有值
if($valueArray[$field]===false)
{
self::error("function ".__FUNCTION__.":the field <b>".$field."</b> is no exist in hash key <b>".$key."</b>");
}
$result=self::$redisObj->hdel($key,$field);
return boolval($result);
}
/**
* [push 列表添加函数]
* @param [string] $key [列表键名]
* @param [string] $li [元素值]
* @param string $pushType [left/right]
* @return [boolean] [是否添加成功]
*/
public function push($key,$li,$pushType='left')
{
self::stringCheck($key,__FUNCTION__);
$action=$pushType=='right'?'rPush':'lPush';
$result=self::$redisObj->$action($key,$li);
return boolval($result);
}
/**
* [getList 列表获取函数]
* @param [string] $key [键名]
* @param integer $range [索引范围]
* @return [array] [结果数组]
*/
public function getList($key,$range="0->-1",$isSerialize=false)
{
$rangeArray=$this->rangeToArray($range);
if(!$isSerialize)
{
return self::$redisObj->lrange($key,$rangeArray[0],$rangeArray[1]);
}
else
{
$result = self::$redisObj->lrange($key,$rangeArray[0],$rangeArray[1]);
foreach($result as $value)
{
$return[]=unserialize($value);
}
return $return;
}
}
/**
* [pop 列表弹出函数]
* @param [string] $key [键名]
* @param string $popType [弹出类型 right/left]
* @return [string] [弹出元素]
*/
public function pop($key,$popType="right",$waitingTime=false)
{
self::stringCheck($key,__FUNCTION__);
if($waitingTime===false)
{
$action=$popType=='left'?'lPop':'rPop';
return self::$redisObj->$action($key);
}
else
{
$action=$popType=='left'?'blPop':'brPop';
return self::$redisObj->$action($key,$waitingTime);
}
}
/**
* [getListLen 列表长度获取函数]
* @param [string] $key [键名]
* @return [int] [长度]
*/
public function getListLen($key)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->llen($key);
}
/**
* [getEleByListIndex description]
* @param [string] $key [键名]
* @param [int] $index [索引]
* @return [string] [元素值]
*/
public function getEleByListIndex($key,$index)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->lindex($key,$index);
}
/**
* [setEleByListIndex 根据索引设置list元素值函数]
* @param [string] $key [键名]
* @param [int] $index [索引]
* @param [boolean] $value [是否设置成功]
*/
public function setEleByListIndex($key,$index,$value)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->lSet($key,$index,$value);
}
/**
* [removeListEle 列表元素移除函数]
* @param [string] $key [键名]
* @param [int] $removeNum [删除个数 >0:从表头开始N个 <0:从表尾开始N个 =0:所有 ]
* @param [string] $eleValue [删除元素的值]
* @return [int] [实际删除的个数]
*/
public function removeListEle($key,$removeNum,$eleValue)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->lrem($key,$eleValue,$removeNum);
}
/**
* [cutList 链表裁剪函数]
* @param [string] $key [键名]
* @param [integer] $start [截取开始索引]
* @param integer $end [截取结束索引,默认-1截取到最后]
* @return [boolean] [截取是否成功]
*/
public function cutList($key,$start,$end=-1)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->ltrim($key,$start,$end);
}
/**
* [addSet 集合添加成员函数]
* @param [string] $key [键名]
* @param [string] $member [成员]
*/
public function addSet($key,$member)
{
self::stringCheck($key,__FUNCTION__);
$result = self::$redisObj->sAdd($key,$member);
return boolval($result);
}
/**
* [getSet 获取集合成员函数]
* @param [string] $key [键名]
* @return [array] [成员数组]
*/
public function getSet($key)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->sMembers($key);
}
/**
* [isInSet description]
* @param [string] $members [成员]
* @param [string] $key [键名]
* @return boolean [是否在集合中]
*/
public function isInSet($members,$key)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->sismember($key,$members);
}
/**
* [countSet 集合成员数量统计函数]
* @param [string] $key [键名]
* @return [integer] [成员数量]
*/
public function countSet($key)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->scard($key);
}
/**
* [randMemberFromSet description]
* @param [string] $key [键名]
* @param integer $numbers [数量]
* @return [array] [成员数组]
*/
public function randMemberFromSet($key,$numbers=1)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->sRandMember($key,$numbers);
}
/**
* [removeMemberFromSet 从集合中移除成员]
* @param [string] $key [键名]
* @param [string] $member [成员]
* @return [boolean] [是否移除成功]
*/
public function removeMemberFromSet($key,$member)
{
self::stringCheck($key,__FUNCTION__);
$result = self::$redisObj->srem($key,$member);
return boolval($result);
}
/**
* [moveSetMemberTo 成员移动函数]
* @param [string] $member [成员]
* @param [string] $sourceSet [源集合]
* @param [string] $targetSet [目标集合]
* @return [boolean] [是否成功]
*/
public function moveSetMemberTo($member,$sourceSet,$targetSet)
{
self::stringCheck($sourceSet,__FUNCTION__);
self::stringCheck($targetSet,__FUNCTION__);
$result = self::$redisObj->smove($sourceSet,$targetSet,$member);
return boolval($result);
}
/**
* [getDiffFromSets 获取多个集合的差集函数]
* @param [string] $sets [多个集合]
* @return [array] [差集数组]
*/
public function getDiffFromSets(...$sets)
{
return self::$redisObj->sDiff(...$sets);
}
/**
* [getInterFromSets 获取多个集合交集函数]
* @param [string] $sets [多个集合]
* @return [array] [交集数组]
*/
public function getInterFromSets(...$sets)
{
return self::$redisObj->sInter(...$sets);
}
/**
* [getUnionFromSets 获取多个集合并集函数]
* @param [string] $sets [多个集合]
* @return [array] [并集数组]
*/
public function getUnionFromSets(...$sets)
{
return self::$redisObj->sUnion(...$sets);
}
/**
* [addZset 有序集合成员添加/更新 函数]
* @param [string] $key [键名]
* @param [array] $zsetArray [数组 键值=>分数]
*/
public function addZset($key,$zsetArray)
{
self::stringCheck($key,__FUNCTION__);
$scoreArr=[$key];
foreach($zsetArray as $k => $v)
{
array_push($scoreArr,$v,$k);
}
$result = self::$redisObj->zAdd(...$scoreArr);
return boolval($result);
}
/**
* [getZsetByIndexRange 根据索引获取有序集合函数 默认顺序 /倒序]
* @param [string] $key [键名]
* @param integer $range [索引范围]
* @param boolean $scoreShow [是否显示分数]
* @param string $order [排序规则 默认顺序]
* @return [type] [成员数组]
*/
public function getZsetByIndexRange($key,$range="0->-1",$scoreShow=false,$order="ASC")
{
self::stringCheck($key,__FUNCTION__);
$rangeArray=$this->rangeToArray($range);
$action = $order === 'ASC'? 'zRange':'zReverseRange';
return self::$redisObj->$action($key,$rangeArray[0],$rangeArray[1],$scoreShow);
}
/**
* [getZsetByScoreRange 根据分数获取有序集合成员函数 ]
* @param [type] $key [键名]
* @param [type] $start [开始分数]
* @param [type] $end [结束分数]
* @param boolean $scoreShow [是否显示分数]
* @param string $order [结果排序]
* @return [type] [成员数组]
*/
public function getZsetByScoreRange($key,$range,$scoreShow=true,$order="ASC")
{
self::stringCheck($key,__FUNCTION__);
$rangeArray=$this->rangeToArray($range);
$scoreShow=$scoreShow?array("withscores"=>true) : array("withscores"=>false);
if($order==='ASC')
{
return self::$redisObj->zRangeByScore($key,$rangeArray[0],$rangeArray[1],$scoreShow);
}
else
{
return self::$redisObj->zRevRangeByScore($key,$rangeArray[1],$rangeArray[0],$scoreShow);
}
}
/**
* [countZset 获取有序集合总成员数量]
* @param [string] $key [键名]
* @return [integer] [成员数量]
*/
public function countZset($key)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->zCard($key);
}
/**
* [countZsetByScoreRange 获取分数区间内成员数量]
* @param [string] $key [键名]
* @param [string] $range [分数范围]
* @return [integer] [成员数量]
*/
public function countZsetByScoreRange($key,$range)
{
self::stringCheck($key,__FUNCTION__);
$rangeArray=$this->rangeToArray($range);
return self::$redisObj->zCount($key,$rangeArray[0],$rangeArray[1]);
}
/**
* [getZsetScore 根据成员获取分数值]
* @param [string] $key [键名]
* @param [string] $member [键值]
* @return [float] [分数]
*/
public function getZsetScore($key,$member)
{
self::stringCheck($key,__FUNCTION__);
return self::$redisObj->zScore($key,$member);
}
/**
* [getZsetIndex 获取成员排名(索引)]
* @param [string] $key [键名]
* @param [string] $member [键值]
* @param [string] $order [排序]
* @return [integer] [排名(索引)]
*/
public function getZsetRank($key,$member,$order="ASC")
{
self::stringCheck($key,__FUNCTION__);
$action = $order==='ASC'?'zRank':'zRevRank';
return self::$redisObj->$action($key,$member);
}
/**
* [removeZsetMember 删除zSet成员函数]
* @param [type] $key [description]
* @param [type] $members [description]
* @return [type] [description]
*/
public function removeZsetMembers($key,$members)
{
self::stringCheck($key,__FUNCTION__);
self::arrayCheck($members,__FUNCTION__);
$memberArray[]=$key;
foreach($members as $k => $v)
{
array_push($memberArray,$v);
}
$result = self::$redisObj->zRem(...$memberArray);
return boolval($result);
}
/**
* [removeZsetMembersByScore zSet根据分数范围删除成员]
* @param [string] $key [键名]
* @param [string] $scoreRange [分数范围]
* @return [boolean] [是否删除成功]
*/
public function removeZsetMembersByScore($key,$scoreRange)
{
self::stringCheck($key,__FUNCTION__);
$rangeArray=$this->rangeToArray($scoreRange);
$result = self::$redisObj->zRemRangeByScore($key,$rangeArray[0],$rangeArray[1]);
return boolval($result);
}
/**
* [alterZsetScore zSet分数操作函数]
* @param [string] $key [键名]
* @param [string] $value [成员]
* @param [integer] $range [变更范围]
* @return [float] [修改后结果]
*/
public function alterZsetScore($key,$value,$range)
{
self::stringCheck($key,__FUNCTION__);
if(!$range)
{
self::error("function ".__FUNCTION__.": the changed range can not be 0 ");
}
$score=$this->getZsetScore($key,$value);
//是不是数字类型
self::numberCheck(abs($value),__FUNCTION__);
return self::$redisObj->zIncrBy($key,$range,$value);
}
/**
* [storeInterFromZsets 多个zSet求交集并存储,存储结果的score是交集每个成员score的和]
* 调用提示:
* $redis->storeInterFromZsets('myZsetAll',array('myZset1','myZset2'));
*/
public function storeInterFromZsets(...$zSets)
{
return self::$redisObj->zinterstore(...$zSets);
}
/**
* [storeUnionFromZsets 多个zSet求并集并存储,存储结果的score是并集成员score的和]
* @param [type] $zSets [description]
* 调用提示
* $redis->storeUnionFromZsets('myZsetAll',array('myZset1','myZset2'));
*/
public function storeUnionFromZsets(...$zSets)
{
return self::$redisObj->zunionstore(...$zSets);
}
}