持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
概述
Redis的底层数据结构一共有6种,分别是字符串、list列表、hash、集合和有序结合。
-
底层数据结构
- 压缩列表:数组,数组中的每一个元素都对应保存一个数据,表头有三个字段zllbytes,zltail和zllen,表尾还有zlend。查头和尾很方便,其他元素就是逐个查找。在目前的版本中基本已经废弃
- listpack 也叫紧凑列表,它的特点就是用一块连续的内存空间来紧凑地保存数据,同时为了节省内存空间,listpack 列表项使用了多种编码方式,来表示不同长度的数据,这些数据包括整数和字符串。
- 跳表:在链表的基础上增加了多级索引,通过索引位置的跳转,实现数据的快速定位。在多级索引上跳来跳去,最后定位到元素
详解
1. 字符串
Redis的字符串是动态字符串和int,是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
- 字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。
应用场景
- 计数器 如果字符串的内容是一个整数,那么还可以将字符串当成计数器来使用。
- 过期和删除 字符串可以使用del指令进行主动删除,可以使用expire指令设置过期时间,到点会自动删除,这属于被动删除。可以使用ttl指令获取字符串的寿命。
- 缓存对象
- 分布式锁
- 共享session信息
2. List双向链表
- 负下标,表示倒数第几个
- 队列/堆栈,结合使用rpush/rpop/lpush/lpop四条指令
- 定长列表:维持定长列表的指令是ltrim,需要提供两个参数start和end,表示需要保留列表的下标范围,范围之外的所有元素都将被移除。
- 快速列表:将链表和压缩列表结合变成quicklist,将多个压缩列表使用双向指针串起来使用
- 在 Redis 3.2 版本之后,List 数据类型底层数据结构就只由 quicklist 实现了,替代了双向链表和压缩列表。
应用场景
- 消息队列
3. hash
hash,使用二维结构,第一维是数组,第二维是链表,hash的内容key和value存放在链表中,数组里存放的是链表的头指针。
-
扩容:当hash内部的元素比较拥挤时(hash碰撞比较频繁),就需要进行扩容。扩容需要申请新的两倍大小的数组,然后将所有的键值对重新分配到新的数组下标对应的链表中(rehash)。
-
渐进式rehash:同时保留新旧两个hash结构,在后续的定时任务和hash结构的读写指令中将旧结构的元素组建迁移到新的结构中,避免扩容导致的线程卡顿。
在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。一般对象使用String+Json存储,对象中某些频繁变化的属性可以考虑抽出来用Hash类型存储。
应用场景
- 购物车
4. set和sortedset
-
set,使用hash结构或者是整数集合实现的,所有的value指向同一个内部值
-
sortedset,实现两个数据结构,一个是hash,一个是跳跃列表,hash作用是关联元素value和权重score,保障元素value的唯一行,通过元素value找到相应的score值。跳跃列表的目的在于给元素value排序,根据score的范围获取元素列表。
- 一方面它等价于Java的数据结构
Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。 - 跳跃列表:层级制串联所有数据
- 一方面它等价于Java的数据结构
应用场景:
- set:点赞和共同关注,抽奖活动
- sortedset:最新列表,排行榜
总结
Redis底层数据结构的实现
- string:简单动态字符串或者int
- 列表:双向链表+压缩列表,后来变成quicklist(双向+压缩列表的综合体)
- hash:压缩列表+哈希表,后来压缩列表变成listpack数据结构来实现
- 集合:哈希表+整数数组
- 有序集合:跳表+压缩列表
如果想要高效使用Redis,记住口诀:单元素操作是基础;范围操作很耗时,统计操作通常高效,例外情况有几个
参考文献
极客:Redis核心设计和实践
通俗易懂的Redis数据结构基础教程:juejin.cn/post/684490…