背景
Redis提供了丰富且结构简单的数据结构类型,在开发中被大量使用,接下来就来盘点下Redis的各种常用数据结构,以及他们的内部编码实现和使用场景。
数据结构
String
字符串类型是Redis最基础的数据结构。首先键都是字符串类型,而且其他几种数据结构都是在字符串类型基础上构建的。字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。
内部编码
字符串类型的内部编码有3种:
- ·int:8个字节的长整型。
- ·embstr:小于等于39个字节的字符串。
- ·raw:大于39个字节的字符串。
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
使用场景
-
缓存:由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
-
计数:许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源。
-
共享Session:使用Redis将用户的Session进行集中管理,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取。
-
限流:在实现限流器时,可以使用 String 类型来存储访问次数,通过 INCR 命令来增加访问计数,并检查是否超过了限流阈值。利用Redis实现限流功能,例如一些网站限制一个IP地址不能在一秒钟之内访问超过n次等。
-
分布式锁:可以使用 String 类型来实现分布式锁的功能,通过 SETNX 命令来尝试设置一个键,如果键不存在则设置成功,表示获取了锁。
Hash
几乎所有的编程语言都提供了哈希(hash)类型,它们的叫法可能是哈
希、字典、关联数组。在Redis中,哈希类型是指键值本身又是一个键值对
结构,形如value={{field1,value1},...{fieldN,valueN}}。
内部编码
哈希类型的内部编码有两种:
-
·ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
-
·hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
使用场景
-
配置信息:Hash 类型可以用来存储配置信息,例如应用的设置或偏好。每个配置项可以作为 field,配置值作为 value 存储。
-
购物车:可以使用 Hash 数据类型来存储购物车中的商品信息。以用户 ID 作为键,每个商品 ID 作为 field,商品数量作为 value。这样可以方便地添加商品、更新数量、删除商品以及获取整个购物车的内容。
-
对象存储:在应用程序中,可以使用 Hash 来存储各种对象的信息,例如商品信息、博客文章、用户配置等。对象的属性可以作为 field,属性值作为 value 存储
需要注意的是哈希类型和关系型数据库有不同之处:
-
·哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的field,而关系型数据库一旦添加新的列,所有行都要为其设置值(即使为NULL)。
-
·关系型数据库可以做复杂的关系查询,而Redis去模拟关系型复杂查询开发困难,维护成本高。
List
列表(list)类型是用来存储多个有序的字符串。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
列表类型有两个特点:第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。第二、列表中的元素可以是重复的。
内部编码
列表类型的内部编码有两种。
-
·ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
-
·linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
-
Quicklist:从 Redis 3.2 版本开始,List 数据类型开始使用 quicklist 作为底层数据结构。quicklist 是 ziplist 和 linkedlist 的混合体,它由多个 ziplist 组成,每个 ziplist 可以独立地增长和收缩,而当 ziplist 达到一定大小后会转换为 linkedlist 结构。这种结构既保证了性能,又节省了内存,适用于大型列表。
使用场景
-
消息队列:List 可以被用作消息队列,其中元素从一端插入(例如使用
LPUSH
)和从另一端移除(例如使用RPOP
)。这种模式支持先进先出(FIFO)的消息处理,适用于异步任务处理和事件通知。 -
最新(文章)列表:可以使用 List 来存储最新的元素,例如最新评论、最新消息等。通过
LPUSH
将新元素添加到列表的开始,并通过LRANGE
获取最新元素的列表。 -
任务队列:在多步骤处理的任务中,可以使用 List 来存储待处理任务的队列。任务可以被加入队列,并在处理时逐个移除。
实际上列表的使用场景很多,在选择时可以参考以下口诀:
- ·lpush+lpop=Stack(栈)
- ·lpush+rpop=Queue(队列)
- ·lpsh+ltrim=Capped Collection(有限集合)
- ·lpush+brpop=Message Queue(消息队列)
Set
集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一
样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过
索引下标获取元素。
Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多实际问题。(如下图中,两个集合的交集、差集、并集操作)
内部编码
集合类型的内部编码有两种:
- ·intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
- ·hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
使用场景
-
标签系统:在社交网络或博客类应用中,可以用 Set 存储用户的兴趣标签,方便进行分类和检索。
-
共同好友:在社交网络中,可以存储用户好友的集合,通过计算两个用户 Set 的交集,找出他们的共同好友。
-
共同关注:类似于共同好友,可以用 Set 存储用户关注的其他用户,通过交集操作找出共同关注的对象
集合类型的应用场景通常为以下几种:
- ·sadd=Tagging(标签)
- ·spop/srandmember=Random item(生成随机数,比如抽奖)
- ·sadd+sinter=Social Graph(社交需求)
Zset
有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。
但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。
有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能,合理的利用有序集合,能帮助我们在实际开发中解决很多问题。
内部编码
有序集合类型的内部编码有两种:
-
·ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplistentries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。
-
·skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。
使用场景
-
排行榜系统:Zset 非常适合实现各种排行榜,如游戏排行榜、社交媒体影响力排行榜等。可以将用户的 ID 作为元素,用户的分数(如经验值、获得的赞等)作为分数进行存储和排序。
-
时间线:可以使用 Zset 来实现时间线功能,例如,将发布的消息作为元素,消息的发布时间戳作为分数,以此来存储和排序所有的消息。
-
带权重的队列:Zset 可以用于实现带权重的任务队列,例如,可以将任务作为元素,任务的优先级作为分数,然后使用 Zset 来存储和排序所有的任务。
-
延时队列:可以将需要延时处理的任务作为元素,任务的执行时间作为分数,然后使用 Zset 来存储和排序所有的任务。可以定期扫描 Zset,处理已经到达执行时间的任务