美文网首页
Redis 常见 7 种使用场景 (PHP 实战)

Redis 常见 7 种使用场景 (PHP 实战)

作者: liamu | 来源:发表于2018-05-09 12:24 被阅读34次

    Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
    为了方便查看redis我这个装了一个redis客户端Redis desktop Manager


    场景一:字符串缓存

    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379,3);//短链接,本地host,端口为6379,超过1秒放弃链接
    $flag = $redis->auth('*****');//登录验证密码,返回【true | false】
    if (!$flag) {
        echo 'redis连接失败';die();
    }
    $redis->select(15);//选择redis库,0~15 共16个库
    // ==== 案例一:简单字符串缓存实战 ===
    $strCacheKey  = 'Test_amu';
    
    // SET 应用
    /*
    $arrCacheData = [
        'name' => 'job',
        'sex'  => '男',
        'age'  => '30'
    ];
    $redis->set($strCacheKey,json_encode($arrCacheData));
    $redis->expire($strCacheKey,30); # 设置30秒后过期
    $json_data = $redis->get($strCacheKey);
    $data = json_decode($json_data);
    */
    
    // HSET 应用
    $arrWebSite = [
        'google' => [
            'google.com',
            'google.com.hk'
        ],
    ];
    $redis->hSet($strCacheKey,'google',json_encode($arrWebSite['google']));
    $json_data = $redis->hGet($strCacheKey,'google');
    $data = json_decode($json_data);
    
    print_r($data);
    
    set&hSet.png

    场景二:简单队列实战

    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379,3);//短链接,本地host,端口为6379,超过1秒放弃链接
    $flag = $redis->auth('****');//登录验证密码,返回【true | false】
    if (!$flag) {
        echo 'redis连接失败';die();
    }
    $redis->select(15);//选择redis库,0~15 共16个库
    // ==== 案例二:简单队列存实战 ===
    $strQueueName = 'Test_amu_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 '------- 进队列成功 ------'.PHP_EOL;
    
    // 查看队列
    $strCount = $redis->lrange($strQueueName,0,-1);
    echo '当前队列数据为:'.PHP_EOL;
    print_r($strCount);
    
    // 出队列
    $redis->lpop($strQueueName);
    echo '----- 出队列成功 -----'.PHP_EOL;
    
    // 查看队列
    $strCount = $redis->lrange($strQueueName,0,-1);
    echo '当前队列数据为:'.PHP_EOL;
    print_r($strCount);
    

    运行结果

    [root@localhost ~]# curl 127.0.0.1:6666/test2.php
    ------- 进队列成功 ------
    当前队列数据为:
    Array
    (
        [0] => {"uid":1,"name":"Job"}
        [1] => {"uid":2,"name":"Tom"}
        [2] => {"uid":3,"name":"John"}
    )
    ----- 出队列成功 -----
    当前队列数据为:
    Array
    (
        [0] => {"uid":2,"name":"Tom"}
        [1] => {"uid":3,"name":"John"}
    )
    
    
    queue

    实战三:发布与订阅

    • 发布
    ini_set('default_socket_timeout',-1);
    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379);
    $flag = $redis->auth('****');//登录验证密码,返回【true | false】
    if (!$flag) {
        echo 'redis连接失败';die();
    }
    $redis->select(15);//选择redis库,0~15 共16个库
    // ==== 案例三:简单发布订阅实战 ===
    $strChannel = 'Test_amu_channel';
    
    // 发布
    $redis->publish($strChannel,"来自{$strChannel}频道的推送");
    echo '消息推送成功'.PHP_EOL;
    $redis->close();
    

    运行结果:

    [root@localhost ~]# curl 127.0.0.1:6666/test2.php
    消息推送成功
    

    redis订阅结果

    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "Test_amu_channel"
    3) (integer) 1
    
    
    
    1) "message"
    2) "Test_amu_channel"
    3) "\xe6\x9d\xa5\xe8\x87\xaaTest_amu_channel\xe9\xa2\x91\xe9\x81\x93\xe7\x9a\x84\xe6\x8e\xa8\xe9\x80\x81"
    
    
    • 订阅
    ini_set('default_socket_timeout',-1);
    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379);
    $flag = $redis->auth('****');//登录验证密码,返回【true | false】
    if (!$flag) {
        echo 'redis连接失败';die();
    }
    $redis->select(15);//选择redis库,0~15 共16个库
    // ==== 案例三:简单发布订阅实战 ===
    $strChannel = 'Test_amu_channel';
    
    // 订阅
    echo '--- 订阅中,等待消息推送 ---'.PHP_EOL;
    $redis->subscribe([$strChannel],'callBackFun');
    function callBackFun($redis,$channel,$msg){
        print_r([
            'redis'   => $redis,
            'channel' => $channel,
            'msg'     => $msg
        ]);
    }
    

    运行结果:

    [root@localhost userauth]# php test2.php 
    --- 订阅中,等待消息推送 ---
    Array
    (
        [redis] => Redis Object
            (
            )
    
        [channel] => Test_amu_channel
        [msg] => 来自Test_amu_channel频道的推送
    )
    
    

    实战四:计数器实战

    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379);
    $flag = $redis->auth('****');//登录验证密码,返回【true | false】
    if (!$flag) {
        echo 'redis连接失败';die();
    }
    $redis->select(15);//选择redis库,0~15 共16个库
    // ==== 案例四:简单计数器实战 ===
    $strKey = 'Test_amu_comments';
    
    // 设置初始值
    $redis->set($strKey,0);
    
    $redis->INCR($strKey); // +1
    $redis->INCR($strKey);
    $redis->INCR($strKey);
    
    $strNowCount = $redis->get($strKey);
    echo '当前数量'.$strNowCount.PHP_EOL;
    

    运行结果:

    [root@localhost userauth]# curl 127.0.0.1:6666/test2.php
    当前数量3
    
    counts.png

    实战五:计数器实战

    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379);
    $flag = $redis->auth('****');//登录验证密码,返回【true | false】
    if (!$flag) {
        echo 'redis连接失败';die();
    }
    $redis->select(15);//选择redis库,0~15 共16个库
    // ==== 案例五:简单排行榜实战实战 ===
    $strKey = 'Test_amu_score';
    
    // 存储数据
    $redis->zadd($strKey,50,json_encode(['name'=>'Tom']));
    $redis->zadd($strKey,70,json_encode(['name'=>'John']));
    $redis->zadd($strKey,90,json_encode(['name'=>'Jerry']));
    $redis->zadd($strKey,30,json_encode(['name'=>'Job']));
    $redis->zadd($strKey,100,json_encode(['name'=>'LiMing']));
    
    $dataOne = $redis->ZREVRANGE($strKey,0,-1,true);
    echo '从大到小排序'.PHP_EOL;
    print_r($dataOne);
    
    $dataOne = $redis->ZRANGE($strKey, 0, -1, true);
    echo '从小到大排序'.PHP_EOL;
    print_r($dataOne);
    

    运行结果:

    [root@localhost userauth]# curl 127.0.0.1:6666/test2.php
    从大到小排序
    Array
    (
        [{"name":"LiMing"}] => 100
        [{"name":"Jerry"}] => 90
        [{"name":"John"}] => 70
        [{"name":"Tom"}] => 50
        [{"name":"Job"}] => 30
    )
    从小到大排序
    Array
    (
        [{"name":"Job"}] => 30
        [{"name":"Tom"}] => 50
        [{"name":"John"}] => 70
        [{"name":"Jerry"}] => 90
        [{"name":"LiMing"}] => 100
    )
    
    

    实战六:简单字符串悲观锁实战

    解释:悲观锁(Pessimistic Lock), 顾名思义,就是很悲观。
    每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。
    场景:如果项目中使用了缓存且对缓存设置了超时时间。
    当并发量比较大的时候,如果没有锁机制,那么缓存过期的瞬间,
    大量并发请求会穿透缓存直接查询数据库,造成雪崩效应。

    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379);
    $flag = $redis->auth('****');//登录验证密码,返回【true | false】
    if (!$flag) {
        echo 'redis连接失败';die();
    }
    $redis->select(15);//选择redis库,0~15 共16个库
    // ==== 案例六:简单字符串悲观锁实战 ===
    $Key = 'Test_amu_lock';
    
    // 获取锁
    $is_lock = lock($Key,10,$redis);
    if($is_lock) {
        echo 'get lock success'.PHP_EOL;
        // 开始业务代码
        unlock($Key,$redis);
    } else {
        echo '访问过于频繁'.PHP_EOL;
    }
    
    /**
     * 释放锁
     * @param  String  $key 锁标识
     * @return Boolean
     */
    function unlock($key = '',$redis){
        return $redis->del($key);
    }
    /**
     * 获取锁
     * @param  String  $key    锁标识
     * @param  Int     $expire 锁过期时间
     * @return Boolean
     */
    function lock($key = '', $expire = 5,$redis) {
        $is_lock = $redis->setnx($key, time()+$expire);
        //不能获取锁
        if(!$is_lock){
            //判断锁是否过期
            $lock_time = $redis->get($key);
            //锁已过期,删除锁,重新获取
            if (time() > $lock_time) {
                unlock($key);
                $is_lock = $redis->setnx($key, time() + $expire);
            }
        }
    
        return $is_lock? true : false;
    }
    

    运行结果:

    [root@localhost userauth]# curl 127.0.0.1:6666/test2.php
    get lock success
    [root@localhost userauth]# curl 127.0.0.1:6666/test2.php
    访问过于频繁
    

    实战七:乐观锁实战

    乐观锁(Optimistic Lock), 顾名思义,就是很乐观。
    每次去拿数据的时候都认为别人不会修改,所以不会上锁。
    watch命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。
    也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。
    注意watch的key是对整个连接有效的,事务也一样。
    如果连接断开,监视和事务都会被自动清除。
    当然了exec,discard,unwatch命令都会清除连接中的所有监视。

    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379);
    $flag = $redis->auth('****');//登录验证密码,返回【true | false】
    if (!$flag) {
        echo 'redis连接失败';die();
    }
    $redis->select(15);//选择redis库,0~15 共16个库
    // ==== 案例七:简单事务的乐观锁实战 ===
    $strKey = 'Test_amu_age';
    
    $redis->set($strKey,10);
    $age = $redis->get($strKey);
    
    echo '事务前:年龄'.$age.PHP_EOL;
    $redis->watch($strKey);
    $redis->set($strKey,20);
    // 开启事务
    $redis->multi();
    
        // 在这个时候新开了一个新会话执行
        $redis->set($strKey,80);
        echo '设置了80'.PHP_EOL;
        $redis->set($strKey,90);
        echo '设置了90'.PHP_EOL;
        $redis->set($strKey,91);
        echo '设置了91'.PHP_EOL;
        
    $rob_result = $redis->exec();
    print_r($rob_result);
    
    unset($age);
    $age = $redis->get($strKey);
    echo '事务后:年龄'.$age.PHP_EOL;
    //当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败
    

    运行结果:

    [root@localhost userauth]# curl 127.0.0.1:6666/test2.php
    事务前:年龄10
    设置了80
    设置了90
    设置了91
    事务后:年龄20
    

    扩展:使用watch完成秒杀抢购功能

    使用redis中两个key完成秒杀抢购功能,increate_num用于存储抢购数量和userList用户存储抢购列表。

    它的优点如下:

      1. 首先选用内存数据库来抢购速度极快。
      1. 速度快并发自然没不是问题。
      1. 使用悲观锁,会迅速增加系统资源。
      1. 比队列强的多,队列会使你的内存数据库资源瞬间爆棚。
      1. 使用乐观锁,达到综合需求。
    $redis = new redis();  
    $redis->connect('127.0.0.1', 6379);
    $flag = $redis->auth('*****');
    if (!$flag) {
        echo 'redis连接失败';die();
    }
    $redis->select(15);
    $increate_num = $redis->get("increate_num");  
    $rob_total = 4;   //抢购数量  
    if($increate_num<$rob_total){  
        $redis->watch("increate_num");  
        $redis->multi();  
     
        //业务代码 
    
        $redis->hSet("userList","userid".mt_rand(1, 9999),time());  
        $redis->set("increate_num",$increate_num+1);  
        $rob_result = $redis->exec();  
        if($rob_result){  
            $userList = $redis->hGetAll("userList");  
            echo "抢购成功!<br/>";  
            echo "剩余数量:".($rob_total-$increate_num-1)."<br/>";  
            echo "用户列表:<pre>";  
            var_dump($userList);  
        }else{  
            echo "手气不好,请重新抢购!";exit;  
        }  
    }  else {
        echo "活动已结束";exit;  
    }
    

    欢迎关注个人微信公众号:

    不惑小年轻.jpg

    相关文章

      网友评论

          本文标题:Redis 常见 7 种使用场景 (PHP 实战)

          本文链接:https://www.haomeiwen.com/subject/lkhnrftx.html