首页 / Redis 入门教程 / 31. 内存优化与对象编码

Redis 入门教程

31. 内存优化与对象编码

本教程共 40 篇 · 第 31 篇 · 更新于 2026-08-02

内存优化对象编码listpackintsetembstrbigkey碎片

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 128zset-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:nameuser:1:age……):每个键都要有独立的 redisObject 头、过期字典条目、哈希表桶开销,内存膨胀明显。
  • 用 1000 个 Hash,每个 Hash 存该用户的字段:在字段少时触发 listpack 紧凑编码,内存通常能省下数倍。

所以:同一对象的多个字段,优先用 Hash 而非一堆 String。这是最实用的一条内存优化经验。

bigkey 优化

bigkey = 单个键的值过大(百万级成员的 Hash/Set、超长 List、几十 MB 的 String)。它带来的问题:

  • 占用内存不均,成为热点,破坏分区/集群的负载均衡(见第 30 章)。
  • DEL 大 key 是同步的,长时间阻塞单线程,引发超时雪崩。

优化手段:

  1. 拆键:把大集合按分片拆成多个小键,如 user:1000:followers:0...:1
  2. 删除用 UNLINKUNLINK 是异步惰性删除(Redis 4+),把释放内存的工作交给后台线程,不阻塞主线程。
127.0.0.1:6379> UNLINK big:hash
(integer) 1
  1. 控制单值体积:尽量别往一个 String 里塞大 JSON/大二进制;必要时压缩或外置到对象存储。
  2. 合理设计元素数量: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 memoryfragmentation_ratio 配合监控告警。

其它内存优化清单

  • 键名短而有度u:1000:nuser:1000:name 省,但别短到无法维护;长键名会放大开销。
  • 设置 maxmemory 与淘汰策略:防止无节制增长,用 allkeys-lru / allkeys-lfu 等按业务选(见第 17 章)。
  • 善用紧凑编码:让小集合停留 listpack/intset,避免一上来就 hashtable。
  • 避免保存临时/可重建数据:能算出来的、能外置的,就别进 Redis。
  • 定期巡检 bigkey:用 redis-cli --bigkeysMEMORY 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 ENCODINGINFO memory)与配置项在 8.x 中保持稳定,本文以 8.10.0 为基线版本。