前言

Redis是一个高性能的分布式内存数据库,在国内外个大互联网公司中都有着广泛的使用,即使是一些非互联网公司也有着非常重要的使用场景。

Redis提供了五种主要的数据类型,它提供了强大且实用的功能,然而实际开发中,有大多数的开发者仅仅只会使用简单的 Redis String的 Get和Set,下面将回顾Redis五大对象,以便能够在实战中游刃有余。

  • String(终究是我扛下来所有)
  • Hash(存储对象我也行)
  • List(栈和队列我都行)
  • Set(标签系统我在行)
  • Sort Set(排起名来我最棒)

字符串

字符串类型是Redis最基础的数据结构,其他几种数据结构都是在字符串类型基础上构建的。字符串类型的值是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频)等。

20211128214708.png

字符串对象的内部编码有3种 :intrawembstr,Redis会根据当前值的类型和长度来决定使用哪种编码来实现:

  • int:如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示;
  • raw:如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节;
  • embstr:如果字符串对象保存的是一个字符串值,并且这个字符申值的长度小于等于32字节;

Reids字符串的使用场景是最为广泛的,甚至有些对redis其它几种对象不太熟悉的人,基本所有场景都会使用字符串(序列化一下直接扔进去),这让本身很单纯的字符串承受了它这个年纪本不该承受的重量。其实Redis的主要使用场景主要有以下几种:

  • 作为缓存层,缓存热点数据;
    • ==热点数据==: 数据库中数据被访问的频率是不均匀的,有些经常被访问的数据称为==热点数据==;
  • Redis字符串可以自增自减的特性可以用来做计数器、限速器、自增ID生成等;
    • ==限速器==:
      • GET + INCR + EXPIRE: 先获取 key 的当前值,如果没有超出限制再执行 INCR 增1,如果 key 不存在,使用 redis 的事务初始化 key 和过期时间。
      • 高并发下的问题: 如果同时10个并发程序执行 GET 返回了 nil, 那么这10个并发程序都会执行 redis 的事务将 key 增一,但每个程序的 count 值都为1,如果 limit 设置的值小于10,那么真正执行的程序就超过限制了。如果执行完事务后再查一次 redis 赋值给 count,那么每个程序可能都会返回10,从而没有程序能够继续执行。 key 已经存在的情况下,先 GET 后 INCR 的逻辑也可能会出现实际执行的程序数多于 limit 的情况。
      • 解决方案:加锁,不允许其他进行访问。
  • 分布式系统的Session共享;
  • 二进制数据的存储;

关于更加详细的关于字符串结构的介绍,参考这篇博客

哈希

哈希对象用来存储一组数据对。每个数据对又包含键值两部分

20211128214758.png

Hash对象也有两种实现方式:ziplist(压缩列表)hashtable(哈希表)

同样,只有当存储的数据量比较小的情况下,Redis才使用压缩列表来实现哈希对象,具体需要满足两个条件

  • 字典中保存的键和值的大小都要小于64字节
  • 字典中键值对的个数要小于512个

当不能同时满足上面的两个条件时,Redis就使用哈希表来实现Hash对象.

当存储的内容是对象的时候,Redis字符串对象很多功能使用Redis 哈希对象也可以实现,如缓存用户信息的时候,使用Redis哈希对象存储,简单直观,如果使用合理可以减少内存空间的使用。

但是也有其缺点,就是要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable将会消耗更多的内存。

此外,Hash对象还可以实现购物车和计数器等功能,更详细的介绍,参考这篇博客

列表

列表这种对象支持存储一组有序的,不重复的数据。因为其有序性,它可以获取指定范围的元素列表,可以在O(1)的时间复杂度获取指定索引的下标的元素等。

20211128214842.png

  • 在Redis3.2版本以前列表类型的内部编码有两种。当满足下面两个条件的时候,Redis 列表对象使用ziplist(压缩列表)来实现。

    • 当列表的元素个数小于list-max-ziplist-entries配置(默认512个)
    • 当列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节)
  • 当列表类型无法满足ziplist条件时,Redis会使用LinkedList作为列表的内部实现。而在Redis3.2版本开始怼列表数据结构进行改造,使用quickList代替了zipListLinkedList

  • 列表类型有两个特点:

    1. 列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。
    2. 列表中的元素可以是重复的。

列表类型可以lpush(左侧push),同时又可以使用rpop(右侧弹出)第一个元素,所以列表类型具有先进先出的特性,可以用来实现消息队列,也可以lpush(左侧push)和lpop(左侧弹出),具有后进先出的特性,因此开发中需要使用栈的时候,我们可以使用列表对象来实现。

使用场景

  • ==消息队列==: 列表类型可以使用 rpush 实现先进先出的功能,同时又可以使用 lpop 轻松的弹出(查询并删除)第一个元素,所以列表类型可以用来实现消息队列;
    • 20211128215029.png
  • ==文章(商品等)列表==: 我们以博客站点为例,当用户和文章都越来越多时,为了加快程序的响应速度,我们可以把用户自己的文章存入到 List 中,因为 List 是有序的结构,所以这样又可以完美的实现分页功能,从而加速了程序的响应速度。
    • 可以分页获取用户文章列表,获取用户id=1的前10篇文章;
    • 使用列表类型保存和获取文章列表会存在两个问题:
      1. 如果每次分页获取的文章个数较多,需要执行多次hgetall操作,此时可以考虑使用Pipeline批量获取,或者考虑将文章数据序列化为字符串类型,使用mget批量获取。
      2. 分页获取文章列表时,lrange命令在列表两端性能较好,但是如果列表较大,获取列表中间范围的元素性能会变差,此时可以考虑将列表做二级拆分,或者使用Redis3.2的quicklist内部编码实现,它结合ziplist和linkedlist的特点,获取列表中间范围的元素时也可以高效完成。

关于List更详细的介绍,参考这篇博客

集合

集合对象是一个无序且唯一的键值集合。它的存储顺序不会按照插入的先后顺序进行存储,与列表不同的是它存储的数据是无序且不重复的。

20211128215003.png

集合类型和列表类型的区别如下:

  • 列表可以存储重复元素,集合只能存储非重复元素;
  • 列表是按照元素的先后顺序存储元素的,而集合则是无序方式存储元素的。

一个集合最多可以存储$2^{32}-1$个元素。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多实际问题。

内部实现

集合类型的内部编码有两种:

  • intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
  • hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。

使用场景

通过上文,我们可以知道集合的主要几个特性,无序、不可重复、支持并交差等操作。因此集合类型比较适合用来数据去重和保障数据的唯一性,还可以用来统计多个集合的交集、错集和并集等,当我们存储的数据是无序并且需要去重的情况下,比较适合使用集合类型进行存储。

标签系统

集合类型比较典型的使用场景是标签(tag)

  • 给用户添加标签。
  • 给标签添加用户。
  • 使用sinter命令,可以来计算用户共同感兴趣的标签
  • 这种标签系统在电商系统、社交系统、视频网站,图书网站,旅游网站等都有着广泛的应用。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。例如一个社交系统可以根据用户的标签进行好友的推荐,已经用户感兴趣的新闻的推荐等,一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数码产品比较感兴趣的人,在各个页面或者通过邮件的形式给他们推荐最新的数码产品,通常会为网站带来更多的利益。

抽奖系统

Redis集合的 SPOP(随机移除并返回集合中一个或多个元素)SRANDMEMBER(随机返回集合中一个或多个元素) 命令可以帮助我们实现一个抽奖系统。

有序集合

有序集合类型 (Sorted Set或ZSet) 相比于集合类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。有序集合保留了集合不能有重复成员的特性(分值可以重复),但不同的是,有序集合中的元素可以排序。

20211128214916.png

有序集合是由 ziplist (压缩列表)skiplist (跳跃表) 组成的。

当数据比较少时,有序集合使用的是 ziplist 存储的,有序集合使用 ziplist 格式存储必须满足以下两个条件:

  • 有序集合保存的元素个数要小于 128 个;
  • 有序集合保存的所有元素成员的长度都必须小于 64 字节。

如果不能满足以上两个条件中的任意一个,有序集合将会使用 skiplist 结构进行存储。

使用场景

有序集合比较典型的使用场景就是排行榜系统。例如学生成绩的排名。某视频(博客等)网站的用户点赞、播放排名、电商系统中商品的销量排名等。我们以博客点赞为例。

排行榜

有序集合比较典型的使用场景就是排行榜系统。例如学生成绩的排名。某视频(博客等)网站的用户点赞、播放排名、电商系统中商品的销量排名等。我们以博客点赞为例

  1. 添加用户赞数

    zadd ranking 10 cir2
    
  2. 取消用户赞数: 这个时候有一个读者又觉得Tom写的不好,又取消了赞,此时需要将文章的赞数从榜单中减去1,可以使用zincrby 。

    zincrby ranking -1 cir2
    
  3. 增加用户赞数

    zincrby ranking 1 cir2
    
  4. 查看某篇文章的赞数

    ZSCORE ranking cir1
    
  5. 展示获取赞数最多的十篇文章

    zrevrange ranking 0 9