ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
> **Redis 场景使用** [TOC] ## 说明 ## **最近在整理Redis的一些使用场景, 包含我在工作当中的例子和网上看到比较好的文章。** ---------- ##参考文章## > 平凡希:https://www.cnblogs.com/xiaoxi/p/7007695.html ## String ## <h5>介绍</h5> String 数据结构是简单的key-value类型,value其实不仅是String,也可以是数字。 常用命令:get、set、incr、decr、mget等。 应用场景:String是最常用的一种数据类型,普通的key/ value 存储都可以归为此类,即可以完全实现目前 Memcached 的功能,并且效率更高。还可以享受Redis的定时持久化,操作日志及 Replication等功能。除了提供与 Memcached 一样的get、set、incr、decr 等操作外,Redis还提供了下面一些操作: 获取字符串长度 往字符串append内容 设置和获取字符串的某一段内容 设置及获取字符串的某一位(bit) 批量设置一系列字符串的内容 使用场景:常规key-value缓存应用。常规计数: 微博数, 粉丝数。 实现方式:String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。 <h5>存储信息1</h5> 用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2种存储方式: ![请输入图片描述][1] 将用户相关的信息转换为JSON字符串,存储在string类型中 $userInfo=['name'=>'depp', 'age'=>'25','sex'=>'age',]; Redis::set("user:1",json_encode($userInfo)); dd(json_decode(Redis::get("user:1"))); 第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。 <h5>存储信息2</h5> 第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的 ![请输入图片描述][2] $shopConfing =[ 's:1:mset'=>1, //是否开启会员体系 's:1:svset'=>1, //是否开启短视频功能 's:1:lset'=>1, //是否开始直播功能 ]; Redis::mset($shopConfing); dd(Redis::keys("shop:1:*")); <h5>计数器</h5> Redis string提供的incr 跟 decr 方法也可以实现简单的计数器功能。 Redis::set("v:z:1",1); //设置点赞 //增加点赞数 Redis::incr("v:z:1",1); //得到点赞数 dd(Redis::get("v:z:1")); > 其他计数器,统计数类似场景:微博的评论数、点赞数、分享数,抖音作品的收藏数,京东商品的销售量、评价数等 <h5>短信验证码小例子</h5> //发送短信证码 $code=rand(1000,9999); // 发送短信处理 /** send message **/ //存储验证码 Redis::setex("code:17600128033","120",$code); // 接收验证码 $inCode="7580"; $mobileNumber="17600128033"; if($inCode == Redis::get("code:".$mobileNumber)){ dd ("验证码正确"); }else{ dd ("验证码错误,验证码为:".Redis::get("code:".$mobileNumber)); } ## Hash ## Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。 常用命令:hget,hset,hgetall 等。 <h5>存储信息3</h5> 除了上述string 存储信息的方式外,我们还可以用Hash类型来存储。 Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口,使得Hash既没有序列化开销和并发问题,用户ID也不会重复存储,非常适合存储对象。 ![请输入图片描述][3] Redis::hset('user:1','name','zhangsan'); Redis::hset('user:1','sex','男'); Redis::hset('user:1','age','20'); dd(Redis::hgetall('user:1')); <h5>注意点</h5> 这里同时需要注意,Redis提供了接口(hgetall)可以直接取到全部的属性数据,但是如果内部Map的成员很多,那么涉及到遍历整个内部Map的操作,由于Redis单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意。 使用场景:存储部分变更数据,如用户信息等。 实现方式: 上面已经说到Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。 <h5>计数、统计</h5> 同时Hash类型灵活的结构也适合给某一类事物进行各种维度的计数。 例如:电商类:商品维度有各种计数(点赞数,评论数,浏览数) 例如:直播类:主播维度有各种计数(动态数、关注数、粉丝数) Redis::hset('video:1','likes',1); //视频点赞数 Redis::hset('video:1','collections',1); //视频收藏 //增加视频、点赞 Redis::hIncrBy('video:1','likes',1); Redis::hIncrBy('video:1','collections',1); //减少点赞数 Redis::hIncrBy('video:1','likes',-1); //获取点赞数 Redis::hget('video:1','goods:4'); <h5>简单购物车</h5> 简单的购物车功能就可以使用Hash结构快速实现。以用户id为key,商品id为field,商品数量为value,恰好构成了购物车的3个要素,如下图所示。 ![请输入图片描述][4] Redis::hset('card:user:1','goods:1','1'); //用户1 增加1个商品1到购物车 Redis::hset('card:user:1','goods:2','2'); //用户2 增加2个商品2到购物车 Redis::hset('card:user:2','goods:1','2'); //用户2 增加2个商品1到购物车 //添加商品购物车 Redis::hset('card:user:1','goods:4','2'); //获取购物车内容 Redis::hgetall('card:user:1'); //增加数量 Redis::hIncrBy('card:user:1','goods:4','2'); //减少数量 Redis::hIncrBy('card:user:1','goods:4','-2'); //删除一个商品 Redis::hdel('card:user:1','goods:4'); //清空购物车 Redis::del('card:user:1'); <h5>hash 和 string的选择</h5> string 和 hash 两种类型都可以用作对象存储。以下的思路可以供大家参考。 当一个对象的属性相对整体而且而且不易变化时,比较适合用string存储 比如: 用户:姓名、年龄、地址、爱好、民族、已婚等等 主播:房间号、姓名、年龄、直播领域 ---------- 当对象的某个属性需要频繁修改,且属性比较零散时,比较适合用hash存储 比如: 用户:喜欢的视频数、喜欢的商品数、点赞数 主播:粉丝数、订阅数 ## List ## <h5>介绍</h5> 常用命令:lpush,rpush,lpop,rpop,lrange等。 应用场景: Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。 List 就是链表,相信略有数据结构知识的人都应该能理解其结构。使用List结构,我们可以轻松地实现最新消息排行等功能。List的另一个应用就是消息队列, 可以利用List的PUSH操作,将任务存在List中,然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作List中某一段的api,你可以直接查询,删除List中某一段的元素。 实现方式: Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。 Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样List即可以作为栈,也可以作为队列。 <h5>消息队列系统</h5> 使用list可以构建队列系统,使用sorted set甚至可以构建有优先级的队列系统。 比如:将Redis用作日志收集器 实际上还是一个队列,多个端点将日志信息写入Redis,然后一个worker统一将所有日志写到磁盘。 取最新N个数据的操作 记录前N个最新登陆的用户Id列表,超出的范围可以从数据库中获得。 //把当前登录人添加到链表里 for ($a=1;$a<20;$a++){ $ret = Redis::lpush("l:uid", $a); //login:user_id } //保持链表只有10位 $ret = Redis::ltrim("l:uid", 0, (10-1)); //获得前10个最新登陆的用户Id列表 dd(Redis::lrange("l:uid", 0, (10-1))); 结果: array:10 [▼ 0 => "19" 1 => "18" 2 => "17" 3 => "16" 4 => "15" 5 => "14" 6 => "13" 7 => "12" 8 => "11" 9 => "10" ] <h5>sina微博热数据:</h5> 在Redis中我们的最新微博ID使用了常驻缓存,这是一直更新的。但是我们做了限制不能超过5000个ID,因此我们的获取ID函数会一直询问Redis。只有在start/count参数超出了这个范围的时候,才需要去访问数据库。 我们的系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。SQL数据库(或是硬盘上的其他类型数据库)只是在用户需要获取“很远”的数据时才会被触发,而主页或第一个评论页是不会麻烦到硬盘上的数据库了。 <h5>秒杀场景的简单实现</h5> 队列的特性在日常开发中还可以用于流量削锋跟解耦。这里做流量削锋的秒杀抢购场景的简单示例。 //监听已抢购的数量 Redis::watch("sk:1:num"); //已经秒杀完的商品数量 $skNum = Redis::hget("sk:h:1",'gum'); //秒杀商品Hash信息 $isSkNum = (int)Redis::get("sk:1:num"); if($isSkNum < $skNum ){ $uid = $this->rand(5);//随机生成用户id // 暂时用setnx 跟 expire 处理限购 问题是并发情况下会不止10个人进来,但是不影响限购啊 // 没有考虑到更好的解决方案,先这么处理吧 // Redis::set("sk:su:1", 1 , 'NX', 'EX', "1000"); 不清楚predis下set 同时设置 NX和EX为什么老是不生效,暂时用下边的方法处理 if(Redis::setnx("sk:su:".$uid,$uid)){ Redis::expire("sk:su:".$uid,10); //设置过期时间,保证10秒内一个用户只能秒杀成功一次 }else{ Rddis::incr('fail'); echo "10秒内允许抢购一次。"; } //上述代码不能放在multi之内。 否则if(Redis::setnx("sk:su:".$uid,$uid)) 会报错 //放在multi当中,相当于未执行,结果不会返回,所以会一直报错 Redis::multi(); Redis::incr('sk:1:num'); Redis::lpush("sk:l:1",$uid); Redis::exec(); }else{ Redis::incr('fail'); echo "不好意思,秒杀已经结束了。"; } ---------- ab 1000请求 100并发的请求结果: 127.0.0.1:6379> get sk:1:num "10" 127.0.0.1:6379> lrange sk:l:1 0 -1 1) "t106A0502910239" 2) "160A50HR21k23R9" 3) "160i7O502f12Y39" 4) "160j5021W23UzZ9" 5) "Wd160c5i0212539" 6) "1Pw60I502Ae1239" 7) "146050RgG213239" 8) "1605p0aL212p3u9" 9) "16050wyAa2123Y9" 10) "16050fi2012f3j9" <h5>热点数据更新</h5> 场景描述:后台在更新咨询。前台用户在观看数据。 比如说数据A B C D E F G,一次取两条数据的, 用户的进来是A B两条数据,此时后台运营人员在后台插入H F两条数据,此时链表变成了H F A B C D E F G,此时用户往上滑如何保证是C D 记录用户后一个值,传到后台,不能下标后移处理。 ## 结尾 ## <p style="background-image: -webkit-linear-gradient(left, #3498db, #f47920 10%, #d71345 20%, #f7acbc 30%,#ffd400 40%, #3498db 50%, #f47920 60%, #d71345 70%, #f7acbc 80%, #ffd400 90%, #3498db);color: transparent;-webkit-text-fill-color: transparent;-webkit-background-clip: text;text-align:center;"> 腹有诗书气自华,最是书香能致远。 </p> [1]: https://blog.zxliu.cn/usr/uploads/2020/11/217422969.png [2]: https://blog.zxliu.cn/usr/uploads/2020/11/1945224260.png [3]: https://blog.zxliu.cn/usr/uploads/2020/11/1321091741.png [4]: https://blog.zxliu.cn/usr/uploads/2020/11/2167948208.png