以下是本人使用redis的场景和对应示例代码:
1.redis连接和常用函数
$redis = new Redis(); //连接redis服务器 $redis->connect('127.0.0.1', 6379); // echo "Connection to server sucessfully <br/>"; //1.设置key和value的值 $redis->set('test','123456'); //2.获取指定key的值 $redis->get('test'); //3.删除指定key的值 $redis->delete('test'); //4.如果不存在该键,则设置。如果存在,则不设置 $redis->setnx('test','456789'); //5.为指定的key设置过期时间 $redis->setex('test',60,'123456'); //6.判断指定的键是否存在 $redis->exists('test'); //7.数字递增 $redis->incr('test'); //8.数字递减 $redis->decr('test'); //9.取得所有指定键的值,如果一个或多个的键不存在,则数组中该键的值返回假 $redis->getMultiple(['test1','test2']); //10.由列表头部添加字符串值 $redis->lpush('test','1111'); //11.由列表尾部添加字符串值 $redis->rpush('test','222'); //12.返回和移除列表中的第一个元素(从左往右) $redis->lpop('test'); //13.返回和移除列表中的第一个元素(从右往左) $redis->rpop('test'); //14.返回列表的长度 $redis->llen('test'); $redis->lsize('test'); //15.返回指定键存储在列表中的指定长度 $redis->lget('test',3); //16。为列表中指定索引赋予新的值 $redis->lset('test',3,'555'); //17.为一个key添加一个值,如果这个值已经在这个key中,则返回false.集合 $redis->sadd('test','111'); $redis->sadd('test','112'); //18.移除key中的value值 $redis->sremove('test','112'); //19.将key1中的值移到key2中 $redis->smove('test1','test2','111'); var_dump($redis->sort('test2'));//排序并返回值 //20.检查集合中是否存在指定的值 $redis->scontains('test','111'); //21.返回集合中存储值得数量 $redis->ssize('test'); //22.移除集合中的指定 key 的一个或多个随机元素,移除后会返回移除的元素。 $redis->spop('test'); //23.返回指定键的交集,如果只指定一个键,那么这个命令生成这个集合的成员。如果不存在某个键,则返回FALSE。 $redis->sinter('test','test1'); //24.执行sinter命令并把交集存储到新建的变量当中 $redis->sinterstore('new','test1','test2'); //25.返回集合中的所有成员 $redis->smembers('news'); $redis->sgetmembers('new'); //26.返回指定键的并集 $redis->sunion('test','test2'); //27.执行sunion命令并把结果存储到新建的变量当中 $redis->sunionstore('new','test','test1'); //28.返回第一个集合中存在并且在其他所有集合中不存在的结果 $redis->sdiff('test','test1'); //29.执行sdiff命令并把结果存储到新建的变量中 $redis->sdiffstore('new','test','test1'); //30.根据参数COUNT的值移除列表中与参数VALUE相等的元素 $redis->lrem('test',0,'111'); //count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。 //count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。 //count = 0 : 移除表中所有与 VALUE 相等的值 //31.为哈希表中的字段赋值 $redis->Hset('test','test-age','55'); //32.返回哈希表中指定字段的值 $redis->Hget('test','test-age'); $redis->Hgetall('test'); //33.返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 $redis->lrange('test',0,-1);//全部的元素
2.比如实现一个简单的日志收集功能或发送大量短信、邮件的功能,实现方式是先将数据收集到队列中,然后有一个定时任务去消耗队列,处理该做的事情。
直接使用redis的rpush,lpop和lpush,rpop
//进队列 $redis->lpush(key, value); //出队列 $redis->rpop(key);
3.比如我们要存储用户信息,ID、姓名、电话、年龄、身高 ,怎么存储?
key = userdata用户ID
hashKey = 姓名,value = xx
hashKey = 电话,value = xx
hashKey = 年龄,value = xx
hashKey = 身高,value = xx
查询时,取出key即可。
//新增 $redis->hSet(key, hashKey, value); $redis->hSet(key, hashKey, value); $redis->hSet(key, hashKey, value); //编辑 $redis->hSet(key, hashKey, value); //查询 $redis->hGetAll(key); //查询所有属性 $redis->hGet(key, hashKey); //查询某个属性
4.互关用户
//关注我的 if(!empty($_GET['uid'])&&!empty($_GET['fansid'])){ $key="user:{$_GET['uid']}:fansid"; $redis->sadd($key,$_GET['fansid']); }else{ echo "关注不成功"; } //我关注的 if(!empty($_GET['uid'])&&!empty($_GET['followsid'])){ $key="user:{$_GET['uid']}:follows"; $redis->sadd($key,$_GET['followsid']); }else{ echo "关注不成功"; } //互关,求2个的交集 $userlist1 = $redis->sinter("user:{$_GET['uid']}:fansid","user:{$_GET['uid']}:follows");
5.排行榜功能
class Rank { private $redis = null; //构造方法,创建redis对象,并连接 public function __construct($ip,$port) { $this->redis = new redis(); $this->redis->connect($ip,$port); } //像zsert类型集合添加元素,包括用户信息和排序用的分数 function set( $key, $score, $userinfo) { //增加一个或多个元素,如果该元素存在,更新他的score if($this->redis->zadd($key,$score,json_encode($userinfo)))//zadd命令用于将一个或多个成员元素及其分数值加入到有序集当中。 { print_r($userinfo); echo "数据添加成功"; } else { echo "数据存在只更新score或者添加失败"; } } //从zsert类型集合种获取全部排行好的用户信息和分数 function get( $key,$withscores=true) { //返回key对应的有序集合中指定区间的所有元素。这些元素按照score从高到低顺序排列 //0代表第一个元素,1第二个,-1代表最后一个,-2倒数第二个 return $this->redis->zrevrange($key,0,-1,$withscores);//Zrevrange 命令返回有序集中,指定区间内的成员。 } } $rank = new Rank('127.0.0.1',6379); //设置A的score $rank->set('Rank',100,array('img'=>'xx.jpg','username'=>'A','userid'=>1)); //设置B的score $rank->set('Rank',250,array('img'=>'xx.jpg','username'=>'B','userid'=>3)); //设置C的score $rank->set('Rank',50,array('img'=>'xx.jpg','username'=>'C','userid'=>2)); //设置C的score $rank->set('Rank',600,array('img'=>'xx.jpg','username'=>'C','userid'=>2)); echo "<pre>"; print_r($rank->get('Rank'));
6.购物车
session_start(); class Cart { private $redis=null; public function __construct() { $this->redis = new Redis(); $this->redis->connect('127.0.0.1',6379); } //向购物车添加商品,修改已经有的商品数量 public function addCart($gid,$cartNum=1) { //根据商品ID查询调用内部方法,模拟从数据库获取商品数据信息 $goodDate = $this->goodsDta($gid); //组合key,seeion_id关联用户,gid关联商品 $key = 'cart:'.session_id().':'.$gid; //将当前用户放入购物车的所有商品ID放到集合种,组合集合key $idskey = 'cart:ids:'.session_id(); //通过key获取对应的商品数量,如果获取到说明商品存在 $pnum = $this->redis->hget($key,'num'); //购物车有对应的商品,只需要添加对应商品的数量 $newNum= $pnum + $cartNum; //判断购物车中是否存在商品,如果不存在 if(!$pnum) { //如果用户传入的数是负数,就证明用户在减少数量,传入的数和原购物车中的商品数量之和应该大于0 if($newNum>0) { //向购物车的商品添加数量 $goodsDta['num'] = $cartNum; //将商品数据存放到redis的hash,使用Hmset一次添加多个字段 $this->redis->hmset($key,$goodsDta); //将商品id存放集合是为了更好的将用户购物车的商品遍历出来 $this->redis->sadd($idskey,$gid); } else { //如果数量小于1,清除购物车商品 if($newNum<1) { $this->redis->del($key); //删除用户购物车的商品 $this->redis->srem($idskey,$gid);//在集合中去掉对应的商品id } else { $this->redis->hset($key,'num',$newNum);//原来的数量加上用户新传入的数量 } } } } //通过商品od删除购物车一条信息,如果没有传gid就清空购物车 public function delCart($gid = false) { //获取当前用户放入购物车中所有商品id集合key $idskey = 'cart:ids:'.session_id(); //如果参数$gid没有传入商品id,清空购物车 if(!$gid) { //去集合拿到商品id foreach ($this->redis->sMembers($idskey) as $key => $id) { //组合一个key,使用session_id和用户关联,使用gid和商品关联 $key = 'cart:'.session_id().':'.$id; } //删除按当前用户购买的商品id集合 $this->redis->del($idskey); } else { //组合一个key,使用session_id和用户关联,使用gid和商品关联 $key = 'cart:'.session_id().':'.$gid; $this->redis->del($key); $this->redis->srem($idskey,$gid); } } //显示用户购物车的所有商品 public function showCartList() { $idskey = 'cart:ids'.session_id(); $idsArr = $this->redis->sMembers($idskey); $list = null; //声明商品列表 $total = 0;//商品总加个变量 foreach ($idsArr as $key => $gid) { //获取当前用户放入购物车中所有商品 $good = $this->redis->hGetAll('cart:'.session_id().':'.$gid); $list[] = $good; //将所有商品的价格汇总 $total +=$good['price'] * $good['num']; } $list['total'] = $total; // 将总金额一并放到商品列表 return $list; } //临时模拟从mysql数据库中获取商品数据 private function goodsDta($gid) { $goodsDta = [ 1 => ['id'=>1,'gname'=>'qwe','price'=>'21'], 2 => ['id'=>2,'gname'=>'22','price'=>'333'], 3 => ['id'=>3,'gname'=>'33','price'=>'444'], 4 => ['id'=>4,'gname'=>'44','price'=>'555'] ]; return $goodsDta[$gid]; } } //简单测试向购物车增,删,改,查等商品的操作 $cart = new Cart(); $cart->addCart(1); // $cart->addCart(2,2); //id为2的商品放入购物车,数量为2 // $cart->addCart(2,1); //修改id为2的商品,原数量加1 // $cart->addCart(3,3); //id为3的商品放入购物车,数量为3 // $cart->addCart(4,-4);// 商品为4的放入,数量-4 // $cart->addCart(3,-1);//id为3的放入,数量-1 echo "<pre>"; print_r($cart->showCartList());//打印购物车列表的金额 // $cart->delCart(2); // print_r($cart->showCartList());//打印购物车列表的金额 // $cart->delCart(); //清空购物车
7.简单队列
$strQueueName = 'Test_bihu_queue'; //进队列 $redis->rpush($strQueueName, json_encode(['uid' => 1,'name' => 'Job'])); $redis->rpush($strQueueName, json_encode(['uid' => 2,'name' => 'Tom'])); $redis->rpush($strQueueName, json_encode(['uid' => 3,'name' => 'John'])); echo "---- 进队列成功 ---- <br /><br />"; //查看队列 $strCount = $redis->lrange($strQueueName, 0, -1); echo "当前队列数据为: <br />"; print_r($strCount); //出队列 $redis->lpop($strQueueName); echo "<br /><br /> ---- 出队列成功 ---- <br /><br />"; //查看队列 $strCount = $redis->lrange($strQueueName, 0, -1); echo "当前队列数据为: <br />"; print_r($strCount);
8.乐观锁防止商品超卖
$redis->watch('sales');//乐观锁 监视作用 set() 初始值0 $sales=$redis->get('sales'); $n=100 if($sales >= $n){ exit('结束'); } //开启事务 $redis->multi(); $redis->incr('sales'); //提交事务 $res=$redis->exec() if($res){ //成功 include 'db.php'; $sql="update products set store=store-1 where id=1"; if($mod->exec($sql)){ echo "完成"; } }else{ exit('失败'); }
9.悲观锁
由于系统并发量较大,并且有频繁的写操作,所以选择悲观锁来控制每个任务只能同时被一个用户领取。主要思路如下:
1、从任务池中找出一部分可分配的任务;
2、根据一定顺序,选择一个任务,作为候选推送任务;
3、尝试对候选推送任务加锁;
4、如果加锁成功,则推送任务给用户,并修改对应的任务状态和用户状态;
5、如果加锁失败,则任务已被领取,重复2-5,直到推送成功。
//加锁 function lock($strMutex,$timeOut){ $res=$redis->set($strMutex,1,'ex',$timeOut,'nx'); if ($res==='OK'){ return true; } return false; } // 定义锁标识 $key = 'Test_bihu_lock'; // 获取锁 $is_lock = lock($key, 10); if ($is_lock) { echo 'get lock success<br>'; echo 'do sth..<br>'; sleep(5); echo 'success<br>'; //解锁 $redis->del($key); } else { //获取锁失败 echo 'request too frequently<br>'; }
10.分布式锁(常用)
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了
<?php class RedLock { private $retryDelay; private $retryCount; private $clockDriftFactor = 0.01; private $quorum; private $servers = array(); private $instances = array(); function __construct(array $servers, $retryDelay = 200, $retryCount = 3) { $this->servers = $servers; $this->retryDelay = $retryDelay; $this->retryCount = $retryCount; $this->quorum = min(count($servers), (count($servers) / 2 + 1)); } public function lock($resource, $ttl) { $this->initInstances(); $token = uniqid(); $retry = $this->retryCount; do { $n = 0; $startTime = microtime(true) * 1000; foreach ($this->instances as $instance) { if ($this->lockInstance($instance, $resource, $token, $ttl)) { $n++; } } # Add 2 milliseconds to the drift to account for Redis expires # precision, which is 1 millisecond, plus 1 millisecond min drift # for small TTLs. $drift = ($ttl * $this->clockDriftFactor) + 2; $validityTime = $ttl - (microtime(true) * 1000 - $startTime) - $drift; if ($n >= $this->quorum && $validityTime > 0) { return [ 'validity' => $validityTime, 'resource' => $resource, 'token' => $token, ]; } else { foreach ($this->instances as $instance) { $this->unlockInstance($instance, $resource, $token); } } // Wait a random delay before to retry $delay = mt_rand(floor($this->retryDelay / 2), $this->retryDelay); usleep($delay * 1000); $retry--; } while ($retry > 0); return false; } public function unlock(array $lock) { $this->initInstances(); $resource = $lock['resource']; $token = $lock['token']; foreach ($this->instances as $instance) { $this->unlockInstance($instance, $resource, $token); } } private function initInstances() { if (empty($this->instances)) { foreach ($this->servers as $server) { list($host, $port, $timeout) = $server; $redis = new Redis(); $redis->connect($host, $port, $timeout); $this->instances[] = $redis; } } } private function lockInstance($instance, $resource, $token, $ttl) { return $instance->set($resource, $token, ['NX', 'PX' => $ttl]); } private function unlockInstance($instance, $resource, $token) { $script = ' if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end '; return $instance->eval($script, [$resource, $token], 1); } }
使用示例:
<?php require_once __DIR__ . '/../src/RedLock.php'; $servers = [ ['127.0.0.1', 6379, 0.01], ['127.0.0.1', 6389, 0.01], ['127.0.0.1', 6399, 0.01], ]; $redLock = new RedLock($servers); while (true) { $lock = $redLock->lock('test', 10000); if ($lock) { print_r($lock); } else { print "Lock not acquired "; } }