Java项目开发中,Redis经常作为缓存中间件的角色而被广泛使用,平时使用最多的操作就是对字符串类型进行set/get了,偶尔还会设置一些键的过期时间。有一天与旁边的老王闲聊,他突然说到一个问题,我们都知道Redis有5种常用的数据结构,但每种数据结构的具体使用场景谁能说出来多少呢?额,这个问题似乎道出了目前的一种现实状况,即难道我们仅仅把Redis当缓存数据库使用?没有点其他追求了?当然不能了。
问题描述:Redis常用的数据结构有哪些?能说一下每种数据结构的使用场景吗?
Redis常用的数据结构有字符串类型(strings)、散列类型(hashes)、列表类型(lists)、集合类型(sets)、有序集合类型(sorted sets),每种数据结构类型的特点,用张图表示,如下:
1、字符串类型(strings)使用场景
基本格式:以set/get命令为例
127.0.0.1:6379> set testkey weixiangxiang
OK
127.0.0.1:6379> get testkey
“weixiangxiang”
常用命令:get/set/mget/mset/getset/del/incr/incrby/decr/decrby/setex/psetex等。
使用场景:数据缓存,计数器,分布式session等,这里提到的数据库默认是mysql。
①:缓存频繁读取但不经常修改的数据(或者称为热点数据)
逻辑:这就是项目中经常使用的功能,前端页面查询热点数据时,先从redis缓存中读取,读取到值就返回,读取不到再去数据库查询返回,同时将查询的数据缓存到redis一份保存,根据需求是否设置过期时间。
注意事项:为了方便,key-value中的值value通常存储的是序列化的json串。
②: 记录网站被用户访问的次数,或网站中的商品被浏览的次数,或限制用户访问次数
逻辑:因为redis是基于内存操作,且网路请求模块使用的是单线程和IO多路复用技术,操作响应特别快,也无并发竞争等问题,相比于使用数据库频繁的去修改数据实现记录访问次数或浏览次数,redis更适合替代这类操作。
注意事项:记录用户访问或浏览次数,key-value中的key通常使用用户ID+网站地址组合而成的字符串,value则使用incr命令实现次数统计;限制用户访问次数的话,key-value中的key设置为用户的IP地址,value记录访问次数,并设置key的过期时间,当key未过期且value超过了访问频次的条件限制,禁止该IP用户访问(或提示xx秒后重试…)。
举例:如CSDN网站的Java专栏中某篇博客的点赞次数,浏览次数等。
③: 实现分布式session,避免重新登录
逻辑:所谓分布式session,就是多个应用服务器共享一个session会话。当用户第一次登录时,将session会话文件存放在redis中,该redis可以独立于所有负载均衡服务器,也可以放在其中一台负载均衡服务器上,但所有应用所在的服务器连接的必须都是同一个redis服务器,保证所有的应用共享一个session会话,从而避免重新登录。
2、列表类型(lists)使用场景
基本格式:以lpush/lrange命令为例
127.0.0.1:6379> lpush demokey Weixiangxiang
(integer) 1
127.0.0.1:6379> lpush demokey Trump
(integer) 2
127.0.0.1:6379> lpush demokey Biden
(integer) 3
127.0.0.1:6379> lrange demokey 0 1
1)“Weixiangxiang”
2)“Trump”
常用命令:lpush/rpush/lpop/rpop/blpop/brpop/rpoppush/brpoppush/lrange/lindex/linsert/lset等。
使用场景:lists是一个元素有序的可重复的列表,可以实现消息队列MQ,栈,定时排行榜等。
①:实现消息队列MQ
逻辑:通过lpush命令从头部入队,通过rpop或brpop命令从尾部出队(符合先入先出的队列模式),反之,可以也通过rpush命令从尾部入队,通过lpop或blpop命令从头部出队。因此,Redis实现消息队列是通过一组 lpush/rpop/brpop 或 rpush/lpop/blpop 命令实现的。
第一种:非阻塞式(lpush + rpop)
说明:把lpush命令操作当作消息的生产者,rpop命令操作当作消息的消费者,举例说明:
第二种:阻塞式(lpush + brpop)
说明:brpop命令格式为brpop key [key …] timeout ,对于同一个Redis客户端,若多个键(如q1,q2)都有元素,则按照从左到右读取第一个键中的一个元素,借此特性可以实现区分优先级的任务队列,,举例说明:
注意事项:第一种如果消息生产者的生产速度大于消费者的消费速度,可能会引发消息积压的问题。另外,实现不停的监听且消费消息,需要不停的调用rpop操作,每调用一次都会发起一次连接,这会造成不必要的资源浪费,因此不推荐使用第一种非阻塞方式。
②: 可分页的定时排行榜
逻辑:将每隔一段时间计算一次的排行榜存储在list列表中,在访问接口时,将page和size分页参数转化成lrange命令即可获取排行榜数据。
注意事项:lists类型适合定时更新的排行榜的实现,但不适合实现实时热榜。
举例:如网易云音乐的热歌TOP排行榜,每周四更新,将用户一周内的歌曲收听才计算一次。
3、散列类型(hashes)使用场景
基本格式:以hmset/hmget命令为例
127.0.0.1:6379> hmset testkey testfield1 tea testfield2 coffee testfield3 coke
OK
127.0.0.1:6379> hmget testkey testfield2 testfield3
1)“coffee”
2) redis coke"
常用命令:hget/hset/hmget/hmset/hgetall/hdel/hincrby/hkeys/hsetnx/hexists/hscan/hvals等。
使用场景:优化strings类型等。
对strings类型的key-value存储进行优化,尤其是多属性的value
逻辑:strings类型的key-value存储中,value通常是序列化的json串(比如商品对象字符串中有多个属性字段),如果频繁修改json串的某个属性,等于要修改整个对象,不灵活且效率低,而将这种模式改为hashes类型,用field存储字段名称,value存储字段名称对应的属性,只需要操作field即可修改某个属性了。
举例:可以参考我的另一篇博客,【记录】优化Redis存储 。
4、集合类型(sets)使用场景
基本格式:以sadd/smembers命令为例
127.0.0.1:6379> sadd testkey svp
(integer) 1
127.0.0.1:6379> sadd testkey mvp
(integer) 1
127.0.0.1:6379> sadd testkey vvp
(integer) 1
127.0.0.1:6379> smembers testkey
1)“mvp”
2)“svp”
3)“vvp”
常用命令:sadd/smembers/sdiff/scan/sinsert/srem/issmember等。
使用场景:sets是字符串的无序排列,基于此可以实现收藏夹功能。
歌曲或网页的收藏功能
逻辑:比如,听音乐时将喜欢的或感兴趣的歌曲可以添加到歌单中,浏览网页时将有用的网站加入收藏夹等等,这里key为用户ID,value则为歌曲ID或网址的集合。
举例:网易云音乐中我收藏的歌单。
5、有序集合类型(sorted sets)使用场景
基本格式:以zadd/zrange命令为例
127.0.0.1:6379> ZADD testkey 1 redis
(integer) 1
127.0.0.1:6379> ZADD testkey 2 mysql
(integer) 1
127.0.0.1:6379> ZADD testkey 3 oracle
(integer) 1
127.0.0.1:6379> ZADD testkey 3 oracle
(integer) 0
127.0.0.1:6379> ZADD testkey 4 oracle
(integer) 0
zrange testkey 0 10 WITHSCORES
1)“redis”
2)“1”
3)“mysql”
4)“2”
5)“oracle”
6)“4”
常用命令:zadd/zcan/zincrby/zcount/zcard/zrem/zrange/zrank等。
使用场景:与sets不同的是,sorted sets每个元素都会关联一个score属性,redis正是通过score来为集合中的成员进行从小到大的排序,可以实现实时排行榜,如小时榜,日榜,周榜等。
实时排行榜
逻辑:实现多种实时的榜单,比如小时榜、日榜榜、周榜等,可以用redis key存储榜单类型,score为点击量,value为事件id,用户每点击一个事件会更新redis数据,sorted set会依据score即点击量将事件id排序。
举例:如微博的1小时榜,24小时榜等。
小结:
至此,整理和分析了redis常用数据结构的应用场景,当然这里所罗列的场景并不能涵盖所有的了,关键之处是要熟悉redis的数据结构和相关命令的特征,这样才能选择合适的方式实现业务需求。
另外,像bitmaps这种结构,它是通过一个bit位来表示某个元素对应的值或者状态,特别适合实现用户签到、活跃用户/在线用户统计等需求,可以参考setbit、bitcount等相关命令;而对于用户人数超多的网站(几千万乃至上亿人数)统计在线人数的话,使用HyperLogLog再合适不过了,它是用来做基数统计的概率算法,优点是在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、且很小的,可以参考pfadd、pfcount等相关命令。
参考:
1. Redis 教程| 菜鸟教程
2. Redis中文官方网站之redis命令
3. 基于Redis实现排行榜周期榜与最近N期榜