★ Redis 有哪些数据结构,底层编码分别是什么?
结论先行:Redis 对外提供 String、List、Hash、Set、ZSet、Stream 等核心结构,以及 Bitmap、HyperLogLog、GEO 等扩展结构;对内则由 int、embstr、raw、listpack、quicklist、intset、hashtable、skiplist 等编码实现,选择哪种编码取决于元素数量与元素大小,且编码只能由紧凑型向散列型单向升级。
一、数据结构与底层编码对照表
| 结构 | 底层编码 | 触发要点 |
|---|---|---|
| String | int / embstr / raw | 整数值用 int;短串用 embstr;长度超过 44 字节自动转 raw |
| Hash | listpack → hashtable | 字段少且值小时用 listpack,否则升级为哈希表 |
| List | quicklist | 由多个 listpack 节点构成的双向链表,兼顾头尾读写与内存 |
| Set | intset → hashtable | 元素全为整数且数量少时用 intset,否则用哈希表 |
| ZSet | listpack → skiplist + hashtable | 规模小用 listpack;规模大用跳表做范围、哈希表做定位 |
| Stream | rax + listpack | 面向消息队列场景,支持消费组与 ack |
二、高频细节与设计原因
- embstr 只分配一次内存(对象头与字节数组连续),raw 需要两次分配,因此短字符串更省内存;
- Redis 7.0 起用 listpack 全面取代 ziplist,消除了 ziplist 元素更新引起的连锁更新(cascade update)问题;
- ZSet 采用哈希表 + 跳表双结构:哈希表按成员 O(1) 取分数,跳表按分数区间顺序遍历,两者共享同一份元素;
- intset 按最大整数的位数在 16/32/64 位之间升级;编码升级是单向的,不可回退到紧凑编码;
- 面试常让比较 3.2 前后的 List:之前是 ziplist 或 linkedlist 二选一,quicklist 是两者的折中方案;
- 相关配置如 hash-max-listpack-entries、set-max-intset-entries 控制转换阈值,生产调参属于加分项;
- 记忆辅助:按 String → Hash → List → Set → ZSet 的顺序把编码、触发条件与版本演进串成一条故事线,背诵效率最高;
- 高频变体:问 String 用什么编码时要先答 int/embstr/raw 三个候选,再补 44 字节阈值,而不是只答 raw。
三、实践排查命令
redis-cli OBJECT ENCODING mykey # 查看某个 key 的底层编码,如 listpack、skiplist
redis-cli TYPE mykey # 查看外层数据结构类型
redis-cli CONFIG GET hash-max-listpack-entries # 查看 Hash 转表的阈值配置
提示:回答时建议带版本意识——5.0 之前是 ziplist,7.0 之后统一叫 listpack,避免被追问细节时穿帮。
常见追问 / 记忆点
- 追问:为什么 ZSet 用跳表而不是红黑树?答:跳表实现简单、天然支持区间遍历,配合哈希表兼顾读写复杂度。
- 追问:embstr 与 raw 的边界是什么?答:Redis 3.2 之后以 44 字节为界,超过即转 raw 且不可逆。
- 追问:Hash 在什么条件下转 hashtable?答:受 hash-max-listpack-entries 与 hash-max-listpack-value 约束,任一超出即升级。
- 记忆点:底层编码由数量 + 大小两个阈值决定、只能小变大,再结合 OBJECT ENCODING 命令实战演示即可答满。