31. 内存优化与对象编码
本教程共 40 篇 · 第 31 篇 · 更新于 2026-08-02
31. 内存优化与对象编码
Redis 是内存数据库,能省 1 字节内存,就多存 1 字节数据、少花 1 分钱。很多同学以为「Redis 吃内存是天生的」,其实 Redis 在底层做了大量精打细算:同一个 Hash,元素少的时候用紧凑编码,元素多了才升级为哈希表;同一个字符串,是整数就存整数,是短串就一次分配。理解这些对象编码(object encoding),你才能真正做到内存优化。
本章码上学带你从「Redis 怎么存数据」讲到「我们怎么存更省」。
本节目标
- 理解 Redis 键/值都是「对象」,且各有底层编码。
- 掌握
OBJECT ENCODING查看编码,并知道 String / Hash / Set / ZSet / List 各自的编码切换阈值。 - 理解
listpack(Redis 7.0 起)如何取代旧版ziplist。 - 掌握 bigkey 的识别与优化手段。
- 了解内存碎片(fragmentation)与主动碎片整理(active defrag)。
Redis 中的「对象」与编码
Redis 里每个键和值都是 redisObject,除了类型(String/Hash/…)外,还有一个 encoding(编码) 字段,指向真正存储数据的底层结构。Redis 会根据数据规模,自动在「紧凑编码」和「通用编码」之间切换:数据小时用省内存的紧凑结构,数据大了再升级为功能完整但占用更大的结构。
用 OBJECT ENCODING 可以查看某个键当前使用的编码:
127.0.0.1:6379> SET n 100
OK
127.0.0.1:6379> OBJECT ENCODING n
"int"
各类型的编码细节
字符串 String:int / embstr / raw
- int:值能表示为 64 位有符号整数时,直接以整数存储,最省。
- embstr:短字符串(Redis 8.x 下长度 ≤ 44 字节)用 embstr,键和值一次性分配内存,紧凑高效。
- raw:更长的字符串用 SDS(简单动态字符串)存储。
127.0.0.1:6379> SET s hello
OK
127.0.0.1:6379> OBJECT ENCODING s
"embstr"
127.0.0.1:6379> SET big "(一个超过 44 字节的很长的字符串……)"
OK
127.0.0.1:6379> OBJECT ENCODING big
"raw"
哈希 Hash:listpack / hashtable
- 元素少且值短时,用 listpack(紧凑的连续内存结构)。
- 超过阈值后升级为 hashtable(真正哈希表)。
阈值由 redis.conf 控制:hash-max-listpack-entries 512(字段数上限 512)、hash-max-listpack-value 64(单个值字节上限 64)。
127.0.0.1:6379> HSET user:1 name tom age 20
(integer) 2
127.0.0.1:6379> OBJECT ENCODING user:1
"listpack"
当你不断往里加字段直到超过 512 个,或某个值超过 64 字节,编码会自动变成 hashtable。
集合 Set:intset / hashtable
- 元素全为整数且数量不多时,用 intset(整数集合,紧凑数组)。
- 否则升级为 hashtable。阈值为
set-max-intset-entries 512。
有序集合 ZSet:listpack / 跳表+字典
- 元素少且值短时用 listpack。
- 否则用「跳表(skiplist)+ 字典」组合,保证范围查询与按分查找都高效。阈值为
zset-max-listpack-entries 128、zset-max-listpack-value 64。
列表 List:quicklist(节点内 listpack)
列表在 Redis 7.0+ 由 quicklist 实现:它把链表分段,每段持有一个 listpack,由 list-max-listpack-size -2 控制每段大小(-2 表示每节点约 8 KB)。
流 Stream:listpack
Stream 在底层同样用 listpack 紧凑存储条目,配合基数树索引。
提示(7.x → 8.x 编码演进):在 Redis 7.0 之前,上述小数据紧凑结构叫 ziplist,相关配置名为
hash-max-ziplist-entries等。Redis 7.0 起 ziplist 被 listpack 全面取代(listpack 更易正确实现、更少指针错误),配置项也统一更名为*-max-listpack-*。本文基于 Redis 8.x(最新稳定版),你看到的就是listpack体系,旧教程里的ziplist名词在新版本中已不再使用。
为什么要关心编码
因为编码直接决定内存占用。举个例子,存储 1000 个用户对象:
- 用 1000 个 String 键(
user:1:name、user:1:age……):每个键都要有独立的 redisObject 头、过期字典条目、哈希表桶开销,内存膨胀明显。 - 用 1000 个 Hash,每个 Hash 存该用户的字段:在字段少时触发
listpack紧凑编码,内存通常能省下数倍。
所以:同一对象的多个字段,优先用 Hash 而非一堆 String。这是最实用的一条内存优化经验。
bigkey 优化
bigkey = 单个键的值过大(百万级成员的 Hash/Set、超长 List、几十 MB 的 String)。它带来的问题:
- 占用内存不均,成为热点,破坏分区/集群的负载均衡(见第 30 章)。
DEL大 key 是同步的,长时间阻塞单线程,引发超时雪崩。
优化手段:
- 拆键:把大集合按分片拆成多个小键,如
user:1000:followers:0、...:1。 - 删除用
UNLINK:UNLINK是异步惰性删除(Redis 4+),把释放内存的工作交给后台线程,不阻塞主线程。
127.0.0.1:6379> UNLINK big:hash
(integer) 1
- 控制单值体积:尽量别往一个 String 里塞大 JSON/大二进制;必要时压缩或外置到对象存储。
- 合理设计元素数量:Hash/Set/ZSet 保持元素规模在编码阈值附近,享受 listpack/intset 的紧凑红利。
内存碎片治理
Redis 使用 jemalloc 等分配器,长期运行后可能出现内存碎片:实际向 OS 申请的内存(used_memory_rss)大于 Redis 真正使用的(used_memory),比值 mem_fragmentation_ratio 偏大。
用 INFO memory 观察:
127.0.0.1:6379> INFO memory
used_memory:1024000
used_memory_rss:1433600
mem_fragmentation_ratio:1.40
mem_fragmentation_ratio 在 1.0~1.3 属健康;持续偏高说明碎片多。Redis 提供主动碎片整理:
activedefrag yes
开启后,Redis 会在空闲时逐步把碎片内存重新紧凑,把 mem_fragmentation_ratio 拉回健康区间。还可用 INFO memory 的 fragmentation_ratio 配合监控告警。
其它内存优化清单
- 键名短而有度:
u:1000:n比user:1000:name省,但别短到无法维护;长键名会放大开销。 - 设置
maxmemory与淘汰策略:防止无节制增长,用allkeys-lru/allkeys-lfu等按业务选(见第 17 章)。 - 善用紧凑编码:让小集合停留 listpack/intset,避免一上来就 hashtable。
- 避免保存临时/可重建数据:能算出来的、能外置的,就别进 Redis。
- 定期巡检 bigkey:用
redis-cli --bigkeys或MEMORY USAGE定位大键。
redis-cli --bigkeys
该命令会扫描并报告各类数据类型中最大的键,是定位 bigkey 的利器。
常见误区
- 误区:Hash 一定比 String 省。 只有当字段多、且能触发 listpack 紧凑编码时才明显省;字段极少时差异不大,但用 Hash 组织数据通常更清晰。
- 误区:碎片率越低越好。 略高于 1 是常态;强行追求 1.0 可能因频繁整理影响性能,保持 1.0~1.3 即可。
- 误区:DEL 删大 key 没事。 大 key 用
DEL会长时间阻塞,务必UNLINK。
小结
- Redis 用「对象 + 编码」在内存上精打细算:小数据用 listpack/intset/embstr/int,大了才升级 hashtable/skiplist。
- 用
OBJECT ENCODING观察编码;阈值由*-max-listpack-*、set-max-intset-entries等控制。 - Redis 7.0+ 以 listpack 取代 ziplist,8.x 沿用 listpack 体系。
- bigkey 用「拆键 + UNLINK」治理;碎片用
activedefrag主动整理,盯紧mem_fragmentation_ratio。 - 最实用一招:同一对象的多个字段,用 Hash 而非一堆 String。
提示:官方 redis.io 下载页另将 8.8 标注为 “Latest stable”,而 GitHub 上的最新发布 tag 为 8.10.0。对象编码(listpack/intset/embstr)相关命令(
OBJECT ENCODING、INFO memory)与配置项在 8.x 中保持稳定,本文以 8.10.0 为基线版本。