• PHP 操作redis常用方法代码


    以下是本人使用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);//全部的元素
    View Code

    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(); //清空购物车
    View Code

    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.分布式锁(常用)

    为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

    1. 互斥性。在任意时刻,只有一个客户端能持有锁。
    2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
    3. 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
    4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了
    <?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
    ";
        }
    }

      

  • 相关阅读:
    用免费Scrum工具Leangoo思维导图 实现影响地图
    mysql-线程模型
    mongodb-锁
    mongodb-mmapv1存储引擎解析(转)
    netty-read
    netty-bind
    netty-eventloop
    java基础-Executor
    ehcache3-源码简析三
    ehcache3-源码简析二
  • 原文地址:https://www.cnblogs.com/jackzhuo/p/12957115.html
Copyright © 2020-2023  润新知