redis 7.0 的 listpack 是替代 ziplist 的紧凑型编码结构,通过移除前项长度字段、采用绝对偏移量数组和紧凑头部编码,减少元数据开销并提升内存规整度,从而降低 jemalloc 内部碎片;其优势在中小规模静态数据中显著,但牺牲随机访问性能且不支持原地变长更新。

Redis 7.0 的 listpack 是什么,为什么它能降低内存碎片
Redis 7.0 把老的 ziplist 彻底替换成 listpack,这不是简单改名,而是重构了小对象的内存布局逻辑。关键点在于:listpack 去掉了 ziplist 中每个 entry 前的「前一个元素长度」字段,改为用「绝对偏移量数组」+「紧凑头部编码」方式组织数据。这直接减少了每个元素的元数据开销,也让内存分配更规整——jemalloc 更容易复用固定尺寸的块,内部碎片自然变少。
典型场景下,存 1000 个 16 字节的字符串:
• ziplist 可能因每个 entry 多出 1–5 字节长度字段 + 对齐填充,总占用浮动在 18–24KB;
• listpack 则稳定在约 16.5KB 左右,且所有元素连续紧排,释放后更容易被整体回收。
listpack 如何影响 jemalloc 的分箱行为
jemalloc 按固定桶大小(如 16/32/64/128 字节)分配内存。ziplist 因结构松散、长度不可预测,常迫使 jemalloc 降级到更大桶(比如本可 32 字节搞定,却分了 64 字节),空闲部分就成了内部碎片。listpack 的 entry 长度高度可控,尤其是小字符串或整数,编码后长度集中在几个离散值上(如 17/19/21 字节),jemalloc 能更精准匹配桶,减少浪费。
- 实测中,相同数据集下
ziplist的mem_fragmentation_ratio常为 1.4–1.7,而listpack普遍压到 1.1–1.3 - 注意:该优势只在
list-max-ziplist-size等参数仍启用 listpack 的前提下生效;若配置过大导致转成quicklist,就绕过 listpack 了 - listpack 不支持嵌套或指针引用,所以它只用于底层存储,上层命令行为无感——你不需要改代码,但得确认没禁用它
升级后碎片率没降?检查这几个配置和使用习惯
listpack 再好,也挡不住错误配置或误用。常见失效点:
-
list-max-ziplist-size设为负数(如 -2)会强制用 quicklist,listpack 彻底不触发 -
hash-max-ziplist-entries或zset-max-ziplist-entries过小,导致频繁从 ziplist/listpack 升级为 hashtable/skiplist,反而加剧碎片 - 大量
LPOP/RPOP混合LPUSH/RPUSH,尤其在 listpack 边界反复操作,会引发多次 realloc,产生外部碎片 - 用
DEBUG OBJECT key查看实际编码:返回encoding: listpack才算生效;若显示quicklist或linkedlist,说明已退化
listpack 的代价:不是所有场景都适合
listpack 的紧凑性是以牺牲随机访问性能换来的。它没有指针,查找第 N 个元素必须从头遍历。所以在高频 LINDEX 或大 offset 的 LRANGE 场景下,延迟可能上升。这不是 bug,是设计取舍。
另外,listpack 不支持原地更新变长数据。比如一个 listpack 元素存的是字符串 "foo"(4 字节),想改成 "foobar"(7 字节),就必须 realloc 整个 listpack —— 这会放大外部碎片风险,尤其在高写入负载下。
真正容易被忽略的是:listpack 的优势只对「中小规模、相对静态」的 list/hash/zset 有效。一旦业务依赖大哈希或长列表的随机读写,别指望靠它大幅降碎片;这时候该拆 key 的拆 key,该换数据结构的换结构。











