Redis 应用场景

编辑于 2023-10-13 07:07:23 阅读 894

首先,总结一下这些应用场景,它们不是独立存在的,很多都还是要依赖mysql;甚至项目初期这些都不是第一选择,很多场景mysql也能做,并且更简单

生成唯一的随机数

很多网站的详情页链接都有一个随机数,比如http://www.cuiwei.net/p/1937090613https://www.zhihu.com/question/48759965https://segmentfault.com/a/1190000041091095

通常的做法是:一个code(id,article_id,code,used_time)表,一个article(id,code, ...)表,在添加文章时从code表选一个未使用的跟这篇文章绑定就可以了,前提是code表要有足够的码

下面重点来了,如何生成唯一的随机数?方法有很多,这里直接介绍使用 Redis 集合

如下,第一批可以直接用,第二批及以后的批次需要和之前的批次求差集,确保我有的你没有才能往数据库里写

    function generateCode($length=5000){
        $codes=[];
        for($i=0; $i<$length; $i++){
            $codes[]=mt_rand(1000000000, 1999999999);
        }
        return $codes;
    }

        $ok = $this->cache()->sAddArray('code', $this->generateCode());//第一批
        $ok2 = $this->cache()->sAddArray('code2', $this->generateCode());//第二批

        $sInter=$this->cache()->sInter('code2', 'code');//交集,我们都有的
        $sDiff=$this->cache()->sDiffStore('code3', 'code2', 'code');//差集,我有的你没有,并将结果保存到code3

        $count=$this->cache()->sCard('code');
        $list=$this->cache()->sMembers('code');
        $count2=$this->cache()->sCard('code2');
        $list2=$this->cache()->sMembers('code2');
        $this->status(0, ['ok'=>$ok, 'ok2'=>$ok2, 'count'=>$count, 'count2'=>$count2, 'sInter'=>$sInter, 'sDiff'=>$sDiff, 'list'=>$list, 'list2'=>$list2]);

抽奖

        $ok = $this->cache()->sAddArray('users', [1,2,3,4,5,6,7,8,9]);
        $users=$this->cache()->sPop('users', 2);//随机选出2名幸运观众,选中后不在参与下面的抽奖
//        $users=$this->cache()->sRandMember('users', 2);//随机选出2名幸运观众,选中后还可以继续参与下面的抽奖
        $this->status(0, ['ok'=>$ok, 'users'=>$users]);

浏览量排行榜

        $ok = $this->cache()->zIncrBy('hotblog', 1, 100);//id为100的文章每次访问加1
        $list=$this->cache()->zRevRangeByScore('hotblog', 30, 10, ['withscores' => TRUE, 'limit' => [0, 10]]);//浏览量在10~30之间的
        $list=$this->cache()->zRevRange('hotblog', 0, 2, true);//浏览量前3的文章
        $list=$this->cache()->zRange('hotblog', 0, 2, true);//浏览量垫底的3篇文章
        $rank=$this->cache()->zRevRank('hotblog', 100)+1;//id为100的文章排第几

互相关注,可能认识

        $this->cache()->sAdd('user_1_fans', 2,3,4,5,6);//user1的粉丝
        $this->cache()->sAdd('user_1_follow', 2,4);//user1的关注
        $this->cache()->sAdd('user_2_fans', 1);//user2的粉丝
        $this->cache()->sAdd('user_2_follow', 1,3);//user2的关注
        //互相关注(看我的关注列表,哪些是互相关注?
        $sInter=$this->cache()->sInter('user_1_fans', 'user_1_follow');
        //可能认识(user1可能认识/喜欢的人?如user1关注了user2,那么user2的关注列表可能是user1喜欢的
        $sDiff=$this->cache()->sDiff('user_2_follow', 'user_1_follow');

点赞

        $this->cache()->sAdd('article_100_like', 1);//user1给article100点赞
        $this->cache()->sRem('article_100_like', 1);//user1取消点赞
        $this->cache()->sIsMember('article_100_like', 1);//user1是否点赞
        $this->cache()->sMembers('article_100_like');//点赞的所以用户
        $this->cache()->sCard('article_100_like');//点赞总数

附近的人

        $r=$this->cache()->geoadd('users', 114.09981,33.585519, 'user1');
        $r=$this->cache()->geoadd('users', 114.070524,33.59067, 'user2');
        $r=$this->cache()->geoadd('users', 113.971066,33.577242, 'user3');

        //两个成员之间的距离
        $r=$this->cache()->geodist('users', 'user1', 'user2', 'km');
        //获取方圆500m的用户
        $r=$this->cache()->geoRadiusByMember('users', 'user1', 500, 'm');

队列

一些耗时的任务,可以加到队列里异步处理。如果想用redis写一个完善的队列是很复杂的,建议使用 beanstalkd、rabbitmq等

        $r=$this->cache()->lPush('list', 2);//左边进
        $r=$this->cache()->rPop('list');//右边出

token登陆令牌

这是我最常用的一个场景,当初从 memcache 切换到 redis 就是因为这个

用户登陆成功会给他设置一个token,并返回给前端;前端每次请求接口会把token带过来,后台验证这个token是否存在,不存在则提示用户重新登陆

        $r=$this->cache()->set('user_1_token', 'aaa', 7*24*3600);//写入token,并设置7天后过期
        $r=$this->cache()->get('user_1_token');//取出

限流

详见 http://www.cuiwei.net/p/1203489253

分布式锁(互斥锁)

如今都是分布式的环境下java自带的单体锁已经不适用的。在 Redis 2.6.12 版本开始,string的set命令增加了一些参数:

  • EX:设置键的过期时间(单位为秒)
  • PX:设置键的过期时间(单位为毫秒)
  • NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
  • XX :只在键已经存在时,才对键进行设置操作。

由于这个操作是原子性的,可以简单地以此实现一个分布式的锁,例如:

set lock_key locked NX EX 1 

如果这个操作返回false,说明 key 的添加不成功,也就是当前有人在占用这把锁。而如果返回true,则说明得了锁,便可以继续进行操作,并且在操作后通过del命令释放掉锁。并且即使程序因为某些原因并没有释放锁,由于设置了过期时间,该锁也会在 1 秒后自动释放,不会影响到其他程序的运行。

示例代码

        $name=$request->get('name');
        $lockKey='withdraw_lock_11111111';

        //正常逻辑判断,如标题已存在 等。。
	
        //上锁
        if ($this->cache()->setnx($lockKey, 'withdraw')) {
            $this->cache()->expire($lockKey, 3);//3秒,防止处理中断,3秒自动解锁
            // 执行业务逻辑

            sleep(1);

            //业务处理完毕,解锁
            $this->cache()->del($lockKey);
            return $this->successResult(0, '', ['name'=>$name]);
        } else {
            return $this->failResult(400, '手速太快了,休息一下吧!');
        }

curl并发测试

curl -Z http://test.cw.net/api/test?name=1 http://test.cw.net/api/test?name=2

缓存穿透

指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,

方案:如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有有写请求进来的话,需要更新缓存哈,以保证缓存一致性,同时,最后给缓存设置适当的过期时间。

缓存雪崩

指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。

缓存击穿

指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db。

有些文章认为它俩区别,是区别在于击穿针对某一热点key缓存,雪崩则是很多key。

互斥锁方案:抢到锁了才能查数据库,否则只能重试查询缓存

参考

https://blog.csdn.net/agonie201218/article/details/123640871

https://blog.csdn.net/weixin_40205234/article/details/124614720

广而告之,我的新作品《语音助手》上架Google Play了,欢迎下载体验