一、Redis 数据结构的核心设计
Redis 的 数据类型(对象) 与 底层数据结构 是解耦的,同一数据类型在不同场景下可能采用不同的底层结构。例如,List 类型在元素较少时使用压缩列表(ZipList),元素较多时则切换为双向链表或 QuickList。
二、核心数据结构与底层实现
数据类型 | 底层数据结构 | 关键特性 |
---|---|---|
String | - SDS(simple dynamic string, 简单动态字符串) - 整数(直接存储数值) | - SDS 支持预分配和惰性释放,避免频繁内存操作 - 最大长度 512MB,二进制安全(可存储图片等二进制数据) |
List | - ZipList (压缩列表)(元素少时) - LinkedList(双向链表)(Redis 3.0前) - QuickList(3.2+,链表+ZipList组合) - ListPack(最新版替代 ZipList) | - QuickList 结合链表和ZipList 优势,减少内存碎片 - ListPack 解决 ZipList 级联更新问题,提升性能 |
Hash | - ZipList (字段少且值小时) - HashTable(哈希表)(默认) | - 哈希表采用链地址法解决冲突,渐进式 Rehash 避免阻塞 - 压缩列表存储时字段和值连续排列,节省内存 |
Set | - IntSet(整数集合)(全为整数时) - HashTable(其他情况) | - IntSet 自动升级存储类型(int16 → int32 → int64) - 哈希表值固定为 NULL,仅用键存储元素 |
ZSet | - ZipList (元素少时) - SkipList(跳表) + HashTable(元素多时) | - 跳表支持 O(logN) 范围查询,哈希表实现 O(1) 单元素访问 - 元素按分值排序,支持排行榜等场景 |
三、底层数据结构详解
-
SDS(简单动态字符串)
- 结构:
len
(长度)、free
(剩余空间)、buf[]
(数据) - 优势:
-
常数复杂度获取长度(
strlen
直接读len
)。 -
避免缓冲区溢出(自动扩容)。
-
支持二进制安全(允许
\0
作为内容)。上述优势都是 c 语言 字符数组不具备的。
-
- 结构:
-
ZipList(压缩列表)
- 应用场景:List、Hash、ZSet 的小数据存储。
- 结构:连续内存块,元素由
prev_len
(前驱节点长度)、encoding
(编码类型)、content
(数据)组成。 - 缺点:级联更新问题(某一节点扩展导致后续节点
prev_len
连锁更新)。
-
QuickList
- 改进ZipList:降低级联更新影响,将一整个ZipList(压缩列表)拆分为多个ZipList;尽可能缩小级联更新范围;
-
ListPack
- 改进ZipList:ZipList级联更新罪魁祸首就是
prev_len
(前驱节点长度);所以 ListPack 去掉了这个前面节点长度的字段,改为记录当前节点的长度,当我们向 ListPack 加入一个新元素的时候,不会影响其他节点的长度字段的变化,从而避免了压缩列表的连锁更新问题。
- 改进ZipList:ZipList级联更新罪魁祸首就是
-
SkipList(跳表)
- 核心逻辑:多层索引加速查找,节点随机层高(基于幂次定律)。
- 优势:支持高效范围查询(如 ZRANGE),复杂度 O(logN)。
- 结构:每个节点包含
score
(分值)、backward
(后退指针)、level[]
(层级数组)。
-
HashTable(哈希表)
- 渐进式 Rehash:扩容时同时操作新旧哈希表,避免单次 Rehash 阻塞服务。
- 冲突解决:链地址法(头插法),哈希函数为 SipHash。
四、版本演进与优化
- Redis 3.0 → 3.2:List 从 ZipList/LinkedList 改为 QuickList(减少内存碎片)。
- Redis 5.0+:废弃 ZipList,引入 ListPack(解决级联更新问题)。
- Redis 7.0:默认使用 ListPack 作为 Hash 和 ZSet 的底层结构。
五、性能优化建议
- 控制数据规模:
- Hash/Set/ZSet 元素较少时优先使用压缩结构(ZipList/ListPack),减少内存占用。
- 避免大 Key:
- String 类型 Value 不超过 10KB,Hash/List 元素数量控制在 1000 以内。
- 合理选择数据类型:
- 频繁范围查询 → ZSet(SkipList)
- 高性能计数 → String(INCR/DECR)
- 去重存储 → Set(IntSet/HashTable)
六、高频面试题
- 为什么 ZSet 同时使用跳表和哈希表?
- 跳表支持范围查询,哈希表实现 O(1) 单元素访问,两者互补。
- Redis 如何解决哈希冲突?
- 链地址法,哈希桶 + 链表结构,新元素插入链表头部。
- ListPack 如何优化 ZipList 的缺陷?
- 取消
prev_len
字段,通过固定长度编码避免级联更新。
- 取消
扩展阅读: