Redis五种数据结构及其使用场景

在这里插入图片描述

先有个概念,redis 数据库其实就是一个大的 map,它容纳了所有的 key, key 都是 string 类型,而 value 则有 string, list, set, hashmap, zset……等类型。

1.String 字符串类型

String是redis中最基本的数据类型,一个key对应一个value。

String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。

这里稍微讲一下什么是二进制安全,一个二进制安全功能(函数),其本质上将操作输入作为原始的、无任何特殊格式意义的数据流。对于每个字符都公平对待,不特殊处理某一个字符。

什么意思呢,举个例子:

C语言中的字符串是根据特殊字符“\0”来判断该字符串是否结束,对于字符串str="0123456789\0123456789”来说,在C语言里面str的长度就是10(strlen(str)=10),所以strlen()函数不是二进制安全的。而在Redis中,strlen str的结果是21,是二进制安全的(Redis底层所使用的字符串表示是Sds),它只关心二进制化的字符串,不关心字符串的具体格式,里面有啥字符,只会严格的按照二进制的数据存取,不会以某种特殊格式解析字符串。

常用的命令有: **set、get、decr、incr、mget、 del ** 等。
在这里插入图片描述

可以参考一下,对应的命令。

实战场景:

  1. 缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。

    举个我曾经做看守所项目时候使用过的例子:做很多流程操作的时候比如家属会见、财务管理等等我们往往都会先判断当前人员是否为在押人员,而判断是否为在押人员是另外一个微服务接口,如果直接实时的去调用那个接口,短时的高并发很有可能把这个服务也拖挂,最终导致整个系统不可用,并且 RPC 本身也是比较耗时的,所以就考虑在这里进行优化。

    那当时我们是怎么做呢?很简单的一个思路,提前将所有的在押人员的user_id存到redis中,这样,当请求到来的时候我们直接通过缓存可以快速判断是否为在押人员。如果不是则直接在这里返回前端。

    通过预先处理减少了实时链路上的 RPC 调用,既减少了系统的外部依赖,也极大的提高了系统的吞吐量。

  2. 计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。一般会使用decr、incr命令用于计数器的实现。

    • 当遇到需求,在规定时间,用户的访问量不能超过规定次数的时候就可以用redis中的计数器来实现了
    • 又可以使用这个技术用来做限流(使用用户的ip作为key,用户访问一次,就加1,如果超过次数就返回false)
    • 可以处理业务上面的的一些访问次数之类的,例如:文章的点赞数,阅读量,允许有一点的延迟效果,先保存到redis中,然后在同步到数据库当中
  3. session:常见方案spring session + redis实现session共享(这块了解的不多)

2.Hash (哈希)

Hash是一个Mapmap,Value值本身又是一种键值对结构,如 value={{field1,value1},…fieldN,valueN}}

在这里插入图片描述

所有hash的命令都是h开头的

常用的命令有: hget 、hset 、hdel、hgetall等 。

在这里插入图片描述

实战场景:

1.缓存: 能直观,相比string更节省空间一些,可以维护缓存信息,如用户信息,视频信息等,但用hash实现的,string也可以实现。

为什么这么说呢?

其实hash类型的(key、field、value)的结构与对象的(对象id,属性,值)的结构相似,也可以用来存储对象。

所以hash也可以用string+json存储对象的一种方式,那么存储对象时,到底用string+json还是用hash呢?

string+jsonhash
效率很高
容量
灵活性
序列化简单复杂

所以说,当对象的某个属性需要频繁修改时,不适合用string+json,因为不够灵活,每次修改都需要重新将整个对象序列化并赋值,如果使用hash类型,则可以针对某个属性单独修改,没有序列化,也不需要修改整个对象。比如,商品的价格、销量、关注数、评论数等可能经常发生变化的属性,就适合存储在hash类型里面。

综上,一般对象用string+json存储,对象中某些频繁变化的属性抽出来用hash存储

2.购物车:以用户id为key,商品id为field,商品数量为value,恰好构成购物车的3个要素。

3.链表

List 说白了就是链表(redis 使用双端链表实现的 List),是有序的,value可以重复,可以通过下标取出对应的value值,左右两边都能进行插入和删除数据。

在这里插入图片描述

常用的命令有:lpush、rpush、lpop、rpop、lrange等。

  • lpush+lpop=Stack(栈)
  • lpush+rpop=Queue(队列)
  • lpush+ltrim=Capped Collection(有限集合)
  • lpush+brpop=Message Queue(消息队列)

在这里插入图片描述

实战场景

  1. timeline:例如微博的时间轴,有人发布微博,用lpush加入时间轴,展示新的列表信息。

  2. 排行榜

    list类型的lrange命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在list类型中,如京东每日的手机销量排行、学校每次月考学生的成绩排名。但是,并不是所有的排行榜都能用list类型实现,只有定时计算的排行榜才适合使用list类型存储,与定时计算的排行榜相对应的是实时计算的排行榜,list类型不能支持实时计算的排行榜

    但是,对于频繁更新的列表,list类型的分页可能导致列表元素重复或漏掉。

    举个例子,当前列表里由表头到表尾依次有(E,D,C,B,A)五个元素,每页获取3个元素,用户第一次获取到(E,D,C)三个元素,然后表头新增了一个元素F,列表变成了(F,E,D,C,B,A),此时用户取第二页拿到(C,B,A),元素C重复了。只有不需要分页(比如每次都只取列表的前5个元素)或者更新频率低(比如每天凌晨更新一次)的列表才适合用list类型实现。对于需要分页并且会频繁更新的列表,需用使用有序集合sorted set类型实现。另外,需要通过时间范围查找的最新列表,list类型也实现不了,也需要通过有序集合sorted set类型实现,如以成交时间范围作为条件来查询的订单列表。

4.Set 集合

集合类型也是用来保存多个字符串的元素,但和列表不同的是集合中 1. 不允许有重复的元素,2.集合中的元素是无序的,不能通过索引下标获取元素,3.支持集合间的操作,可以取多个集合取交集、并集、差集。跟Java的HashSet类似。

在这里插入图片描述

常用的命令有:sset 、srem、scard、smembers、sismember、sadd、spop、sunion等。

在这里插入图片描述

实战场景

1.标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。

2.点赞,或点踩,收藏等,可以放到set中实现

因为Redis为set类型提供了求交集,并集,差集的操作,可以非常方便地实现譬如共同关注、共同爱好、共同好友等功能。

  • sinter交集命令可以获得A和B两个用户的共同好友
  • sismember命令可以判断A是否是B的好友
  • scard命令可以获取好友数量
  • 关注时,smove命令可以将B从A的粉丝集合转移到A的好友集合
  • srandmember命令可以随机展示
  • 当然黑名单白名单也一样,set类型适合存储这些黑名单数据,sismember命令可用于判断用户、ip、设备是否处于黑名单之中。

5.zset 有序集合

有序集合和集合有着必然的联系,它和set一样是不可重复的,区别在于多了score值,用来代表排序的权重。也就是当你需要一个有序的,不可重复的集合列表时,就可以考虑使用这种数据类型。

(有序集合中的元素不可以重复,但是score 分数可以重复,就和一个班里的同学学号不能重复,但考试成绩可以相同)。

在这里插入图片描述

常用的命令有: zadd 、 zrange、 zscore、zrem、zcard等等。

在这里插入图片描述

实战场景:

1.排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐