Listpack替换ziplist是为解决连锁更新、内存碎片和结构退化问题;Redis 7.0默认对hash启用listpack,但需满足hash-max-ziplist-entries≤字段数且hash-max-ziplist-value≥所有value长度,否则退化为hashtable。

listpack 替换 ziplist 不是为了“换个名字”,而是为了解决真实线上压测和崩溃中暴露的连锁更新、内存碎片、结构退化三重问题。Redis 7.0 对 hash 类型底层也统一切换为 listpack,但这个切换**默认生效的前提是配置没被手动破坏**。
为什么 hash 的 ziplist 必须被替换
Redis 早期用 ziplist 存 hash 是为了省空间,但它在 hset/hdel 频繁混合场景下极易触发连锁更新:插入一个稍大的 field 或 value,会导致后续所有 entry 的 prevlen 字段重算,进而引发整块内存 realloc —— 这不仅卡主线程,还会把原本紧凑的内存打散成碎片。而 listpack 完全去掉 prevlen,每个 entry 只存自身长度 + 绝对偏移,更新只影响本 entry,不波及邻居。
listpack 在 hash 中怎么启用
hash 类型是否真用上 listpack,取决于 hash-max-ziplist-entries 和 hash-max-ziplist-value 这两个配置项:
- 只要任一字段超限(比如
hash-max-ziplist-value设为 64,但你存了个 65 字节的 value),Redis 就立刻升级为hashtable,彻底绕过listpack - 这两个值默认是 512 和 64,但如果你在
redis.conf里显式改成0或负数,Redis 会直接跳过listpack创建逻辑,强制走hashtable -
DEBUG OBJECT myhash返回encoding: listpack才算成功;若看到hashtable,说明已退化,再省空间也没意义
listpack 对 hash 内存和淘汰的影响
实测 1000 个 key-value 对(field/value 均为 16 字节字符串):
- 用
ziplist:内存占用浮动在 18–24 KB,mem_fragmentation_ratio常达 1.6+,jemalloc 桶复用率低 - 用
listpack:稳定在 ~16.5 KB,mem_fragmentation_ratio压到 1.1–1.3,相同maxmemory下更晚触发淘汰 - 关键不是“少了 2KB”,而是
listpack编码后长度高度集中(如 17/19/21 字节),完美匹配 jemalloc 的 32B/64B 分配桶,减少内部碎片
容易被忽略的退化陷阱
即使你没改配置,以下操作也会让 hash 悄悄退出 listpack:
- 执行
hset myhash a "x" b "y" c "z"后又hdel myhash b,再hset myhash b "a very long string over 64 bytes"—— 单次超限就不可逆升级 - 用
hmset(已弃用)或客户端 pipeline 批量写入时,某条失败或超限,可能让整个结构提前升级 -
redis-cli --pipe导入数据时未校验 value 长度,批量触发退化
真正起作用的从来不是“Redis 7.0 默认用了 listpack”,而是你有没有守住那两条配置阈值,以及有没有在业务写入路径里埋下超限种子。











