redis 五种数据结构使用场景举例
一.string 字符串数据结构使用场景
1.单值缓存
set key value get key
2. 对象缓存
(1) set user:1 value(json对象) (2) mset user:1:name zhangsan user:1:balacnce 1888 mget user:1:name
第一种方法用于读频繁的,第二种方法用于修改比较频繁的,只需要修改某个字段,而不是整个一条记录对象
3. 分布式锁
setnx product:10001 true setnx product:10001 false
返回为1的结果表示执行成功,还可能设置锁超时(10秒) setnx product:10001 true ex 10 nx
4. 计数器
incr article:readcount :{文章id} get article:readcount :{文章id}
比如用于文章阅读记数。
5. web集群session共享
比如使用asp.net core session
6. 分布式系统全局序列号
incr orderid
比如:分库分表中的主键id, 用自增长id不好解决。用redis自增长来维护主键id。但在并发量高时,每次都与redis交互,redis的资源也有限。 可以先批量生成比如(1~1000)放入内存,在程序中累加。
incrby orderid 1000
7.api访问限流
目的:会员id:10001_action,请求api接口,一分钟不允许超过30次,下面是伪代码
//不存在key,设置key if(!exists(10001)) { //value为访问次数, 60为过期秒数 setnx(10001,1,60); } else { if(get(10001)>30) return "一分钟不允许超过30次"; //添加访问次数 incr(10001); }
二. hash 哈希数据结构应用场景
1.对象缓存 格式(hset key field value)
下面是一个user表,对应的存储如下所示:
hmset user 1:name zhuge 1:balance 1888 hmget user 1:name 1:balance
上面这个存在问题,由于redis内部使用单线程操作,将一个user表的数据全部存入一个key中,这样应用程序端多线程操作同一个key时会出现阻塞。
解决办法使用数据分段:例如对user表创建10个hash, 每个hash的key,分别是 user01,user02.. user10。 存储时,对user表id取模,存入到不同的hash key中.
2.电商购物车
以用户id为key , 商品id为field, 商品数量为value, 如下所示:
购物车操作:
1) 添加商品 hset cart:1001 10088 1
2) 增加数量 hincrby cart:1001 10088 1
3) 商品总数 hlen cart:1001
4) 删除商品 hdel cart:1001 10088
5) 获取购物车所有商品 hgetall cart:1001
3. hash的优点
1)同类数据归类整合储存,方便数据管理
2) 相比string操作消耗内存与cpu更小
3) 相比string存储更节省空间
4. hash的缺点
1)过期功能不能使用在field上,只能用在key上
2) redsi集群架构下不适合大规模使用。因为集群采用的是分片。而分片存储是对key取模定向到不同的数据节点。同一个key只会使用数据全部集中在一个节点。解决:考虑数据分段hash key
三. list 列表数据结构应用场景
格式: lpush key value[value...] 从左边(前)存放
rpush key value[value...] 从右边(后)存放
lpop key 从左边取
rpop key 从右边取
常用的数据结构
stack 栈(先进后出) lpush + lpop
query 队列(先进先出) lpush + rpop
blocking mq(阻塞队列) lpush + brpop
1. 微博消息和微信公众号消息
比如:“张三(id为10)”关注了二个公众号, mactalk, 备胎说车,当发文时,向关注的粉丝们发送消息。
1.mactalk发文,消息id为10018
lpush msg:10:10018
2.备胎说车发文,消息id为10086
lpush msg:10 10086
3. 张三查看最新
lpush msg:10 0 5
取出索引5条
注意:当关注的粉丝数在万以下可以考虑上面的方式,因为redis 单机的QPS能达到10w,插入w条数据也是很快。当某些大V的粉丝数达到100w或1000w这种方式就不行。
2.lpush + rpop 队列(先进先出)
它一般应用在异步消息队列。平时我们习惯于使用rabbitmq或kafka作为消息队列中间件,在应用程序之间增加异步消息传递功能,但使用过rabbitmq的知道使用起来比较复杂,发消息之前要创建Exchange,再创建Queue,还要将Queue与Exchange通过某种规则 绑定起来,发消息的时候要指定routing-key,还要控制头部信息。消费者在消费之前也要进行上面一系列的烦琐过程。
有的redis,可以解脱出来,对于那些只有一组消费者的消息队列,使用redis可以非常轻松搞定,需要注意的是,redis的消息队列不是专业的消息队列,它没有非常多的高级特性(如消息优先级),没有ack保证,如果对消息的可靠性有着极高要求,那么它就不适合使用。
客户端在rpop操作来获取消息,然后进行处理,如此循环,如果队列空了,需要让客户端暂停1秒再取,不然redis的qps会被拉高。如果有多个客户端,可以考虑使用brpop替代rpop。 前缀b代表blocking也就是阻塞读,阻塞读在队列没有数据的时候,会进入休眠状态,一是数据到来,则立即醒过来,消息的延迟几乎为零。
四. set 集合数据结构应用场景
格式 sadd key member [member ...]
srem key member[member..]
1. 微信抽奖小程序
1)点击参与抽奖加入集合
sadd key {userID}
2)查看参与抽奖所有用户
smembers key
3)抽取count名中奖者
srandmember key [count]/sopop key [count]
比如:上面业务抽奖获得实点书籍 共2条,有6个用户参考了该活动
sadd act:101 1888 sadd act:101 1889 sadd act:101 1890 sadd act:101 1891 sadd act:101 1892 sadd act:101 1893
通过以下命令可以随机获取二个中奖的用户,每次执行结果都不一样。结果不会从集合中删除
srandmember act:101 2 //atc:101是key
下面命令是获得抽奖的用户,不能再次抽奖, 结果会从集合中删除
spop act:101 3 //取三个用户
2. 微信点赞,收藏,标签
1)点赞
sadd like:{消息id} {用户id}
2)取消点赞
srem like:{消息id} {用户id}
3) 检查用户是否点过赞
sismember like:{消息id} {用户id}
4) 获取点赞的用户列表
smembers like:{消息id}
5) 获取点赞用户数
scrad like like:{消息id}
3.集合操作
sinter set1 set2 set3 -->{ c} //交集
suntion set1 set2 set3 -->{a,b,c,d,e} //并集
sdiff set1 set2 set3 -->{a} //差集
集合操作实现微博微信关注 模型
1)诸葛老师关注的人:
zhugeset -->{yangguo ,sima,luban}
2) 杨过老师关注的人
yanguo set-->{zhuge,sima,luban,guojia}
3) 我和杨过老师共同关注:
sinter zhugeset yugguoset -->{sima,luban}
4)我关注的人也关注他(杨过老师)
sismember simaset yangguo
sismember lubanset yangguo
5) 我可能认识的人
sdiff yangguoset zhugeset ->(zhuge,guojia)
4.集合操作实现电商商品筛选
sadd brand:huawei p30 sadd brand:xiaomi mi-6x sadd brand:iphone iphone8 sadd os:android p30 mi-6x sadd cpu:brand:intel p30 mi-6X sadd ram:8G p30 mi-6x iphone8 sinter os:android cpu:brand:intel ram:8G -->{ p30, mi-6X}
五. Zset 有序集合数据结构应用场景
1.延迟队列实现
延迟队列可以通过redis的zset(有序集合)来实现,将消息序列化成一个字符串作为zset的value, 消息的到期处理时间作为score,然后用多个线程轮询zset获取到期的任务进行处理,因为多个线程是为了保障可用性,万一挂了一个线程还有其他线程可以继续处理,因为有多个线程,所以需要考虑并发争抢任务,确保任务不会被多次执行。
伪代码如下所示:
----------生产端--------------- var value=jsonstr; //s:2108191942 延时5分钟 var s = DateTime.Now.AddMinutes(5).ToString("yyMMddHHmm"); redis.ZADD("delay-queue",s,value); ----------消费端--------- while(true) { var s = DateTime.Now.ToString("yyMMddHHmm"); var values= redis.zrangebyscore("delay-queue",0,s,0,1); if(value=="") continue; var value= values[0]; //从消息队列中移除该消息 var success=redis.zrem("delay-queue",value) //最后value反序列化出来,进行业务处理 ..... }
redis的zrem方法是多线程多进程争抢任务的关键,它的返回值决定了当前实例有没有抢到任务,因为loop方法可能会被多个线程,多个进程调用,要通过zrem来决定唯一的属主。
参考资料 : .net core中使用redis 实现延迟队列
2.粉丝关注时间排序和学生成绩名次排序
zset可以用来存储粉丝列表,value值是粉丝的用户ID,score是关注时间,我们可以对粉丝列表按关注时间进行排序。
同样也可以用来存储学生的成绩,value值是学生的ID,score是学生的成绩,我们对成绩按分数进行排序就可以得到他的名次。
3. zset限流
参考资料 https://blog.csdn.net/XIANZHIXIANZHIXIAN/article/details/107744599