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 扩缩容时同时保留两张表,并维护迁移游标:

  1. 为新表分配更合适的容量;
  2. 后续增删改查顺带迁移少量桶;
  3. 查找期间同时检查旧表与新表;
  4. 旧表全部迁完后释放,完成切换。

后台空闲维护也会推进 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 偶有额外开销操作同时查询两张表并推进桶迁移

生产中不要依赖某个固定编码完成业务语义;编码是内部实现,升级版本或修改阈值后可能变化。


相关