首页/数据/Redis 进阶速查/ZSet 跳表编码与内存拐点
数据结构与选型42 条

ZSet 跳表编码与内存拐点

有序集合在元素少且小时用 listpack,超过 zset-max-listpack-entries(默认 128)或成员超过 zset-max-listpack-value(默认 64 字节)后转为 skiplist 与哈希表组合。

Redis 进阶速查数据更新于 2026-10-13

ZSet 跳表与内存拐点

有序集合需要同时支持「按成员查分数」和「按分数排序取区间」两类访问,因此内部用了两套结构:一个字典负责成员到分数的映射,一个跳表负责按分数有序排列。当元素少且小时,Redis 用紧凑的 listpack 把成员与分数连续存放,省掉两套结构的指针开销;一旦元素数或成员长度超过阈值,就升级为跳表加字典的组合,查询仍然高效但内存明显上升。

基本信息

两种编码listpack(紧凑)与 skiplist(跳表加字典)
条目阈值zset-max-listpack-entries 默认 128
成员长度阈值zset-max-listpack-value 默认 64 字节
复杂度插入与查找 O(log N),区间取数为 O(log N + M)
查看方式OBJECT ENCODING key

典型使用场景

  • 排行榜容量评估:估算元素数与成员长度,判断榜单是否已经越过编码拐点
  • 内存优化:把长成员名换成短标识,让 ZSet 保持在紧凑编码而不必调整阈值
  • 延时队列与限流:元素数通常不大,多处于 listpack 编码,内存可控
  • 大批量导入前的预判:一次性写入数十万成员会立刻触发升级,内存占用与预期差距很大
  • 排查内存异常上涨:确认是否因为新增成员触发编码切换而非数据量本身增长

观察编码与阈值

观察编码与阈值
redis-cli ZADD rank 100 alice 99 bob
redis-cli OBJECT ENCODING rank                     # listpack
redis-cli CONFIG GET zset-max-listpack-entries     # 默认 128
redis-cli CONFIG GET zset-max-listpack-value       # 默认 64
redis-cli ZADD rank 1 '一个远超六十四字节的长成员名称用于触发编码切换'
redis-cli OBJECT ENCODING rank                     # 已升级为 skiplist
redis-cli MEMORY USAGE rank                        # 对比升级前后的实际占用

编码与开销

编码存储方式内存特点
listpack成员与分数连续存放无额外指针,内存最省,查找为线性扫描
skiplist跳表加字典双结构每个成员存在于两处,内存上升明显
切换条件条目数或成员长度超阈值任一条件满足即升级,且不可逆
影响面升级只针对该键同一次批量写入中的大键会立即升级
控制成员长度比调阈值更有效:阈值调大会让单键包含更多元素,虽然省了指针开销,但对该键做范围查询或整键删除时的单次阻塞时间也随之变长。把成员名从长文本换成紧凑编号,既保持紧凑编码又不增加单次操作负担。
批量写入超大 ZSet 会长时间占用主线程:一条 ZADD 携带数十万对成员值在服务端是单命令执行,期间其他请求全部排队。导入大批量数据应拆成多次小批量写入,并在低峰执行。

易错点

  • 长期用长字符串当成员名,ZSet 很快越过 64 字节阈值升级为跳表,内存占用比预期高出数倍
  • 以为编码升级可以退回,删除大量成员后内存并未下降,紧凑编码不会自动恢复
  • 用一条 ZADD 命令导入几十万成员,主线程被长时间占用,表现为所有客户端同时超时
  • 把大榜单长期保留而不裁剪长度,元素数持续增长,内存与迁移成本同步上升

数据来源与参考

  • Redis 官方文档:Sorted Sets 数据类型
  • redis.conf:zset-max-listpack-entries 注释
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌