Redis 底层数据结构与对象编码
→ 返回 Redis
Redis 对外提供 String、Hash、List、Set、ZSet 等数据类型;内部并不是“一种类型固定一种结构”,而是通过 redisObject 记录逻辑类型、底层编码、LRU/LFU 信息和数据指针,根据元素数量、值大小及版本选择更节省内存或更适合更新的编码。
键空间 dict
key(SDS) ──► redisObject
├─ type:String / Hash / List / Set / ZSet ...
├─ encoding:int / embstr / raw / listpack / hashtable ...
├─ lru:淘汰或 LFU 相关信息
├─ refcount:引用计数
└─ ptr:指向真实数据编码会随 Redis 版本演进。旧资料常见
ziplist、quicklist + ziplist;新版本更多使用listpack。理解设计动机比死记版本阈值更重要,具体阈值以当前redis.conf和OBJECT ENCODING key为准。
SDS:动态字符串
Redis 的 key、普通字符串值和协议缓冲区广泛使用 SDS(Simple Dynamic String),而不是直接把 C 字符串当作唯一表示。
| 机制 | 作用 |
|---|---|
| 保存长度 | STRLEN 获取长度为 O(1),不必扫描到 \0 |
| 保存容量 | 拼接前检查剩余空间,减少重复分配 |
| 二进制安全 | 内容可包含 \0,长度不依赖终止符 |
| 预分配/惰性释放 | 降低连续增长、缩短时的内存分配次数 |
| 多种 header | 按字符串大小使用不同字段宽度,减少小字符串元数据开销 |
String 常见编码:
int:可直接表示为整数的值;embstr:较短字符串,对象头和 SDS 一次连续分配,缓存局部性好;raw:较长或修改后的字符串,对象与 SDS 分开管理。
dict:键空间与哈希表
Redis 数据库键空间本质上是字典;Hash、Set 等在元素较多时也可使用哈希表。典型哈希表通过桶数组定位,再处理哈希冲突。
渐进式 rehash
如果一次性把大量元素从旧表搬到新表,主线程会产生明显停顿。Redis 扩缩容时同时保留两张表,并维护迁移游标:
- 为新表分配更合适的容量;
- 后续增删改查顺带迁移少量桶;
- 查找期间同时检查旧表与新表;
- 旧表全部迁完后释放,完成切换。
后台空闲维护也会推进 rehash。渐进式迁移把一次 O(N) 工作摊到多次操作,但迁移期间会短暂占用两张表的内存。
listpack、quicklist 与整数集合
listpack
listpack 是一段紧凑的连续内存,元素相邻存放并自描述长度。它省去了大量指针和独立对象开销,适合元素少、值小的 Hash/ZSet 等;缺点是中间插入删除可能移动内存,元素变多后操作成本上升。
quicklist
List 需要两端快速插入,又不能让每个小元素都承担链表节点开销。quicklist 将多个紧凑节点串成双向链表:
[listpack] ⇄ [listpack] ⇄ [listpack]它在“连续内存的节省空间”和“链表的局部修改能力”之间折中,并可对中间节点压缩。
intset
Set 全部是整数且规模较小时可使用 intset:有序、连续存储,并按能容纳最大整数的宽度整体升级。加入非整数或规模超过阈值后,通常转为哈希表;这种编码转换一般不可逆。
ZSet:跳表 + 字典
元素较多时,ZSet 通常同时维护:
- dict:
member → score,按成员查分数接近 O(1); - skiplist:按
(score, member)排序,支持范围与排名操作,平均 O(log N)。
跳表通过多层前进指针跨越节点,达到类似平衡树的查找效率,实现和并发调试相对直观。范围查询定位起点后,可沿最底层顺序遍历。
小 ZSet 使用紧凑编码以节省内存,超过元素数量或值长度阈值后再转为跳表相关编码。
编码转换与排查命令
TYPE mykey
OBJECT ENCODING mykey
MEMORY USAGE mykey
DEBUG OBJECT mykey # 生产环境谨慎开放 DEBUG| 现象 | 底层原因 |
|---|---|
| 小 Hash 很省内存 | 元素可连续存于 listpack,指针和对象头少 |
| Hash 突然变大 | 超过阈值后转为 hashtable,换取更稳定的增删改查 |
| 大 List 两端操作快 | quicklist 支持头尾节点局部修改 |
| ZSet 排名高效 | skiplist 维护有序关系,dict 加速成员定位 |
| rehash 偶有额外开销 | 操作同时查询两张表并推进桶迁移 |
生产中不要依赖某个固定编码完成业务语义;编码是内部实现,升级版本或修改阈值后可能变化。