Redis 内存优化完全指南
Redis 的内存不只是 value 本身,还包括 key、RedisObject、字典结构、过期字典、客户端缓冲区、复制缓冲区和内存碎片。 优化核心:用合适的数据结构表达数据,用合理的 TTL 管理生命周期,用正确的淘汰策略保护热点数据,再通过 bigkey、碎片率和缓冲区监控持续治理。
一、Redis 内存占用分析
Redis 的内存由以下几部分组成:
| 类别 | 说明 | 举例 |
|---|---|---|
| key 本身 | 每个 key 的字符串 | user:profile:10001 |
| value 本身 | 存储的数据 | JSON 字符串、Hash field、List element |
| 元数据开销 | RedisObject、字典节点、哈希表 | 每个 key-value 约 ~64 字节额外开销 |
| 过期字典 | TTL 维护 | 大量过期 key 增加内存 |
| 客户端缓冲区 | 每个客户端连接的输入/输出缓冲 | 慢客户端/大批量输出时膨胀 |
| 复制积压缓冲区 | 主从复制 | 由 repl-backlog-size 决定 |
| 内存分配器碎片 | 申请/释放不匹配导致 | 碎片率 > 1.5 需要关注 |
| COW 额外内存 | fork 子进程时写时复制 | RDB/AOF rewrite 期间临时升高 |
所以不要简单理解成:Redis 里存了 1GB 的业务数据,就只占 1GB 内存。
二、核心优化思路
少存:不该放 Redis 的数据不要放
短存:能设置过期时间的 key 尽量设置 TTL
精存:key、value、序列化格式尽量减少冗余
合理存:根据场景选择合适的数据结构
可淘汰:配置合理的 maxmemory 和淘汰策略
可排查:通过监控和命令定位 bigkey、碎片和异常缓冲区
三、Key 设计优化
3.1 控制 Key 长度
不推荐:
user:profile:detail:2026:mobile:login:region:china:id:10001
更合理:
u:p:10001 # 但不要失去可读性
user:profile:10001 # 保留业务含义,去掉重复废话
3.2 Key 命名规范
格式:业务:对象:ID
user:profile:10001
product:detail:20001
order:status:30001
好处:可读性好、方便按前缀扫描和统计。
3.3 大量无 TTL 的缓存 Key
缓存类 key 必须设置过期时间,否则 Redis 只增不减:
- 防止历史数据长期占用内存
- 降低人工清理成本
- 给淘汰策略留出空间
注意: TTL 不要全部设成同一时间,加随机偏移防雪崩。
基础 TTL + 随机偏移,如 30min + 0~5min 随机
四、Value 与序列化优化
4.1 避免大对象无脑 JSON 化
JSON 存在的问题:
- 字段名重复占空间
- 字符串格式比二进制大
- 类名/类型信息额外冗余
- 只更新一个字段时整体读写
例如 {"id":10001,"name":"Tom","age":18,"city":"Shanghai"} — 字段名 id/name/age/city 都是重复开销。
4.2 Spring RedisTemplate 注意点
| 方式 | 优点 | 缺点 |
|---|---|---|
RedisTemplate |
直接存对象,方便 | 可能写入额外类型信息(@class),占用更多内存 |
StringRedisTemplate |
数据更干净 | 序列化/反序列化需自己控制 |
建议: 内存敏感场景下,用 MEMORY USAGE key 实测真实占用,对比不同序列化方式。
4.3 小对象可以考虑 Hash
# String 方式
SET user:10001 '{"name":"Tom","age":18}'
# Hash 方式
HSET user:10001 name Tom age 18 city Shanghai
Hash 优势:可单独修改字段;小 Hash 底层使用紧凑编码(ziplist)。
但 Hash 不是永远更省 — field 名太长、field 太多、value 特别大时也可能膨胀。结合 MEMORY USAGE 实测。
4.4 能用整数不要存字符串
SET user:age:10001 18 # 整数,紧凑编码
# 优于:
SET user:age:10001 "eighteen-years-old" # 长字符串
计数器、库存、点赞数、访问量优先用数字类型。
五、数据结构选型
| 数据类型 | 适合场景 | 内存优化要点 |
|---|---|---|
| String | 简单 key-value、计数器、分布式锁、小 JSON | 控制 value 大小,避免大 JSON 形成 bigkey |
| Hash | 用户信息、商品信息、配置对象(字段少) | 小 Hash 紧凑编码,field 名称别太长 |
| List | 队列、消息列表、最新记录 | 控制长度 + 定期 LTRIM |
| Set | 去重集合、标签集合 | 判断存在可考虑布隆过滤器 |
| Sorted Set | 排行榜、延迟队列 | 只保留 TopN,定期清理低分数据 |
| Bitmap | 签到、在线状态、布尔状态(ID 连续) | 比 Set 省大量空间,但偏移量不能离散 |
| HyperLogLog | UV 统计 | 固定小空间,但有误差,不能记录具体谁 |
5.1 Bitmap 案例:用户签到
# Set 方式(存 100 万用户签到需存 100 万个 ID)
SADD sign:20260424 10001
# Bitmap 方式(按 bit 位记录)
SETBIT sign:20260424 10001 1
Bitmap 代价:偏移量不能过于离散;适合 ID 比较连续的场景。
5.2 HyperLogLog 案例:UV 统计
PFADD page:uv:20260424 user:10001 user:10002
PFCOUNT page:uv:20260424
固定小空间,不精确(有误差),不能记录具体访客身份。
六、过期与淘汰策略
6.1 过期策略
Redis 删除过期 key 依赖两类机制:
| 机制 | 说明 |
|---|---|
| 惰性删除 | 访问 key 时发现过期→删除 |
| 定期删除 | Redis 周期性抽样检查并删除 |
key 到期 ≠ 立刻从内存消失。大量过期 key 未及时被访问,可能短时间继续占用内存。
6.2 淘汰策略(maxmemory-policy)
当 maxmemory 达到上限时:
| 策略 | 含义 | 适用场景 |
|---|---|---|
noeviction |
不淘汰,写入时报错 | Redis 当数据库或强依赖存储 |
allkeys-lru |
淘汰最近最少使用的 key | 通用缓存场景 |
volatile-lru |
淘汰有 TTL 的 key 中最近最少使用 | 只淘汰缓存 key |
allkeys-lfu |
淘汰低频访问 key | 热点明显且访问频率稳定 |
volatile-lfu |
淘汰有 TTL 的 key 中低频访问 | 缓存 key 有 TTL 且保留高频 |
volatile-ttl |
优先淘汰更快过期的 key | TTL 敏感场景 |
allkeys-random |
随机淘汰 | 要求低的简单缓存 |
volatile-random |
随机淘汰有 TTL 的 key | 只淘汰临时缓存 |
缓存型 Redis 推荐: allkeys-lru 或 allkeys-lfu
存储型 Redis: noeviction + 扩容/拆分/持久化保障
七、Bigkey 优化
7.1 什么是 Bigkey
| 类型 | 标准 |
|---|---|
| String | value > 10 KB |
| List | > 10000 个元素 |
| Hash | > 10000 个 field |
| Set / ZSet | > 10000 个 member |
7.2 Bigkey 的危害
不只是占内存,还会导致:
- 网络传输慢
- 序列化/反序列化慢
- 删除时可能阻塞 Redis(单线程)
- 迁移/备份/主从同步成本高
- 集群模式数据倾斜
- 单个命令处理时间变长,影响其他请求
7.3 优化方法
- 拆分 key(按业务维度或用户 ID 拆分)
- 控制集合长度(
LTRIM、ZREMRANGEBYRANK) - 分页读取,不使用一次性全量命令
- 删除大 key 用
UNLINK(异步释放) - 大 Hash 拆成多个小 Hash
# 拆分示例:一个大 Hash user:all
# 按分片拆分:
user:profile:0 → 前 1000 个用户
user:profile:1 → 接下来 1000 个
# 或按用户 ID 拆分:
user:profile:10001
user:profile:10002
八、内存碎片优化
8.1 碎片指标
redis-cli INFO memory
# 重点关注:
# used_memory — Redis 认为自己实际使用的内存
# used_memory_rss — 操作系统分配给 Redis 进程的物理内存
# mem_fragmentation_ratio — 碎片率 = used_memory_rss / used_memory
mem_fragmentation_ratio > 1.5 表示可能存在明显碎片。
8.2 碎片产生原因
- key 频繁创建和删除
- value 大小变化很频繁
- 大量过期删除集中发生
- bigkey 删除或更新
8.3 碎片处理方案
# 开启主动碎片整理
redis-cli CONFIG SET activedefrag yes
其他措施:
- 避免 value 大小频繁剧烈变化
- 大 key 用
UNLINK异步释放 - 低峰期执行内存整理或重启切换
- 扩容/分片减少单实例压力
注意: 碎片整理消耗 CPU,需同时观察延迟和 CPU 使用率。
九、持久化与复制带来的内存问题
9.1 fork 与 Copy On Write
Redis 做 RDB/AOF rewrite 时 fork 子进程,父进程继续处理写请求。被修改的内存页会复制一份 → 短期内内存升高。
生产建议: Redis 内存不能长期打满,预留余量给持久化、复制和碎片。
9.2 AOF 重写压力
AOF rewrite 期间写入流量高 → 额外缓冲 → 内存/磁盘 IO 升高 → rewrite 时间变长 → 主从延迟变大。
9.3 客户端输出缓冲区
如果客户端消费太慢,Redis 输出缓冲区膨胀:
redis-cli CLIENT LIST
# 关注:qbuf、qbuf-free、obl、oll、omem 等字段
排查:客户端是否消费慢、是否一次性读取 bigkey、网络是否异常。
客户端缓冲区详细治理参见 redis-connection-management。
持久化机制原理参见 redis-persistence-strategy(RDB/AOF/混合持久化)。
十、常用排查命令
# 查看整体内存
redis-cli INFO memory
# 查看单个 key 内存(带采样)
redis-cli MEMORY USAGE key SAMPLES 5
# 查看对象底层编码
redis-cli OBJECT ENCODING key
# 内存诊断
redis-cli MEMORY STATS
redis-cli MEMORY DOCTOR
# 扫描 bigkey(低峰期执行)
redis-cli --bigkeys
redis-cli --memkeys
十一、场景案例
电商商品缓存
- 商品详情按商品 ID 拆 key
- 热点商品设较长 TTL 或逻辑过期
- 普通商品 TTL 加随机值
- 大字段(图文详情)不全部放 Redis
- 排行榜 ZSet 只保留 TopN
用户签到
- ID 连续 → Bitmap
- ID 离散大 → 考虑 Bloom Filter 或 Set
热门文章阅读量
INCR article:read:10001
# 计数类用数字 String,不要把整个文章对象反复读写
十二、常见问题排查思路
| 问题 | 优先排查 |
|---|---|
| 内存持续上涨 | 无 TTL key 过多?写入 > 过期/淘汰速度?bigkey?慢客户端缓冲区?持久化额外内存? |
| 突然 OOM | maxmemory 过高未留余量?淘汰策略 noeviction?短时大量大 value?AOF rewrite 高峰? |
| 命中率下降但内存高 | 低价值 key 占位?淘汰策略不合理?TTL 设置不合理?bigkey 挤压热点? |
| 删除 key 后内存未降 | key 过小占比不明显?RSS 未回收?碎片高?缓冲区占用?业务仍在写入? |
关联页面
| 页面 | 关联点 |
|---|---|
| redis-persistence-strategy | RDB/AOF/混合持久化(fork/COW 内存影响) |
| redis-connection-management | 客户端输出缓冲区治理、连接打满 |
| redis-ha-replication-sentinel | 高可用架构(淘汰策略场景参考) |
| redis-backup-recovery | 备份恢复(内存与持久化配合) |
| server-performance-four-dimensions | 系统级内存监控 |
| linux-memory-management-deep-dive | Linux 内存管理深潜(cgroup v2/回收/OOM) |