Redis 持久化、过期删除、淘汰策略与内存碎片全解析 -- pd的后端笔记
文章目录
-
- [Redis 持久化、过期删除、淘汰策略与内存碎片全解析 -- pd的后端笔记](#Redis 持久化、过期删除、淘汰策略与内存碎片全解析 -- pd的后端笔记)
- [🎯 核心问题](#🎯 核心问题)
- [🧠 一、先建立整体认知:Redis 的"内存治理"全景图](#🧠 一、先建立整体认知:Redis 的“内存治理”全景图)
-
- [📊 一张表先把几个概念区分开](#📊 一张表先把几个概念区分开)
- [🚀 二、Redis 的持久化机制有哪些?](#🚀 二、Redis 的持久化机制有哪些?)
-
- [1. 一句话先回答](#1. 一句话先回答)
- [2. RDB:快照型持久化](#2. RDB:快照型持久化)
-
- [✅ 优点](#✅ 优点)
- [⚠️ 缺点](#⚠️ 缺点)
- [3. AOF:日志型持久化](#3. AOF:日志型持久化)
-
- [Redis 7.0 之后的补充](#Redis 7.0 之后的补充)
- [4. RDB 和 AOF 怎么选?](#4. RDB 和 AOF 怎么选?)
-
- [📊 对比表](#📊 对比表)
- [✅ 生产建议](#✅ 生产建议)
- [🔧 三、Redis 在生成 RDB 文件时如何处理请求?](#🔧 三、Redis 在生成 RDB 文件时如何处理请求?)
-
- [1. 先给结论](#1. 先给结论)
- [2. 处理流程图](#2. 处理流程图)
- [3. 为什么还能继续处理请求?](#3. 为什么还能继续处理请求?)
- [4. 那新写入的数据会不会丢?](#4. 那新写入的数据会不会丢?)
- [5. Copy-On-Write 到底在干什么?](#5. Copy-On-Write 到底在干什么?)
-
- [⚠️ 工程影响](#⚠️ 工程影响)
- [6. `SAVE` 和 `BGSAVE` 的区别](#6.
SAVE和BGSAVE的区别) -
- [✅ 面试回答建议](#✅ 面试回答建议)
- [⏰ 四、Redis 数据过期后的删除策略是什么?](#⏰ 四、Redis 数据过期后的删除策略是什么?)
- [🗑️ 五、Redis 中有哪些内存淘汰策略?](#🗑️ 五、Redis 中有哪些内存淘汰策略?)
-
- [1. 先区分"过期删除"和"内存淘汰"](#1. 先区分“过期删除”和“内存淘汰”)
- [2. 经典高频答案:8 种策略](#2. 经典高频答案:8 种策略)
- [3. 如果按最新官方文档看:Redis 8.6 新增了 LRM](#3. 如果按最新官方文档看:Redis 8.6 新增了 LRM)
- [4. LRU、LFU、LRM、TTL 分别适合什么场景?](#4. LRU、LFU、LRM、TTL 分别适合什么场景?)
- [5. 生产里最常见的选择](#5. 生产里最常见的选择)
-
- [场景 1:纯缓存](#场景 1:纯缓存)
- [场景 2:很多 key 不允许被淘汰,只有临时 key 能删](#场景 2:很多 key 不允许被淘汰,只有临时 key 能删)
- [场景 3:热点相对稳定](#场景 3:热点相对稳定)
- [6. 一个容易踩坑的点](#6. 一个容易踩坑的点)
-
- [✅ 面试简答模板](#✅ 面试简答模板)
- [🧩 六、Redis 的内存碎片化是什么?如何优化?](#🧩 六、Redis 的内存碎片化是什么?如何优化?)
-
- [1. 什么叫内存碎片化?](#1. 什么叫内存碎片化?)
- [2. 为什么 Redis 容易出现碎片?](#2. 为什么 Redis 容易出现碎片?)
-
- (1)频繁创建/删除不同大小的对象
- [(2)大 key 修改频繁](#(2)大 key 修改频繁)
- [(3)`fork + Copy-On-Write` 的放大效应](#(3)
fork + Copy-On-Write的放大效应) - (4)内存分配器行为
- [3. 该看哪些指标?](#3. 该看哪些指标?)
-
- [📊 建议先看这几个](#📊 建议先看这几个)
- 一个容易误判的细节
- [4. 如何优化内存碎片?](#4. 如何优化内存碎片?)
- [✅ 方法 1:开启 active defrag](#✅ 方法 1:开启 active defrag)
- [✅ 方法 2:做对象模型优化,减少 churn](#✅ 方法 2:做对象模型优化,减少 churn)
- [✅ 方法 3:给持久化留足内存余量](#✅ 方法 3:给持久化留足内存余量)
- [✅ 方法 4:必要时做 `MEMORY PURGE`](#✅ 方法 4:必要时做
MEMORY PURGE) - [✅ 方法 5:在合适窗口做重启/迁移/主从切换](#✅ 方法 5:在合适窗口做重启/迁移/主从切换)
- [📊 优化思路总结表](#📊 优化思路总结表)
-
- [✅ 面试回答建议](#✅ 面试回答建议)
- [🧾 七、Redis 的虚拟内存(VM)机制是什么?](#🧾 七、Redis 的虚拟内存(VM)机制是什么?)
- [⚠️ 八、这些问题背后真正的工程风险是什么?](#⚠️ 八、这些问题背后真正的工程风险是什么?)
-
- 典型事故链路
- [✅ 更推荐的治理思路](#✅ 更推荐的治理思路)
- [📊 九、面试速查表](#📊 九、面试速查表)
-
- [1. 六个问题的快速答案](#1. 六个问题的快速答案)
- [2. 一段更像面试现场的话术](#2. 一段更像面试现场的话术)
- [✅ 十、总结](#✅ 十、总结)
- [🔗 参考资料](#🔗 参考资料)
🎯 核心问题
Redis 为什么既快又容易成为高频面试题?
因为它看起来只是一个"内存数据库",但背后真正考察的是 4 件事:
- 数据会不会丢:对应持久化机制。
- 内存会不会爆:对应过期删除与淘汰策略。
- 内存为什么看起来越用越多:对应内存碎片化。
- 历史方案为什么被废弃:对应 Redis 的 VM(Virtual Memory)机制。
如果只停留在"RDB 是快照、AOF 是日志"这个层面,其实还不够。
下面我们从一个更工程化的视角,把这几个点串成一条完整链路:
- 数据写进 Redis 后,如何落盘?
- 生成 RDB 文件时,Redis 还能不能继续处理请求?
- key 过期了,Redis 会不会立刻删?
- 内存满了,Redis 到底删谁?
- 明明 key 不多,为什么 RSS 还是很大?
- Redis 以前的 VM 到底是什么,为什么后来不用了?
🧠 一、先建立整体认知:Redis 的"内存治理"全景图
我们先别急着背概念,先看 Redis 对数据和内存的完整处理链路。
RDB
AOF
RDB+AOF
是
否
否
是
是
否
客户端写入数据
数据进入内存
是否开启持久化
按规则触发快照
追加写命令到 AOF
同时保留快照与操作日志
key 是否设置 TTL
惰性删除 + 定期删除
常驻内存
内存是否达到 maxmemory
继续提供服务
触发淘汰策略
是否存在碎片化
active defrag / purge / 重启迁移
稳定运行
📊 一张表先把几个概念区分开
| 主题 | 解决的问题 | 什么时候触发 | 代价 / 风险 |
|---|---|---|---|
| RDB | 宕机后如何恢复数据 | 定时、BGSAVE、主从全量同步 |
可能丢最近一次快照后的数据;fork 有成本 |
| AOF | 提高数据持久性 | 每次写命令后追加日志 | 文件更大,恢复更慢,存在磁盘 I/O 压力 |
| 过期删除 | 已过期 key 怎么清理 | 访问时 / 周期扫描时 | 删除不一定"立刻"发生 |
| 内存淘汰 | 内存满了删谁 | 达到 maxmemory 后 |
可能影响命中率,写入延迟会上升 |
| 内存碎片 | 为什么 RSS 高于真实数据量 | 分配器释放不完全、频繁 churn | 物理内存浪费,fork 期间更危险 |
| VM(历史) | 以前如何把冷数据换出到磁盘 | 超内存时尝试 swap value | 性能复杂、收益不稳定,已废弃 |
到这里你可以把它理解成一句话:
持久化解决"断电后还能不能回来",过期和淘汰解决"内存满了怎么办",碎片化解决"为什么看起来没满却已经很危险"。
🚀 二、Redis 的持久化机制有哪些?
1. 一句话先回答
Redis 的主流持久化机制有两种:
- RDB(Redis Database) :某个时间点的内存快照。
- AOF(Append Only File) :按顺序记录写命令日志。
在工程实践里,常见状态其实有 4 种:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 不开启持久化 | 纯内存,重启即丢 | 纯缓存、可重建数据 |
| 只开 RDB | 定时快照 | 备份、恢复快、能接受分钟级数据丢失 |
| 只开 AOF | 记录写操作 | 对数据安全要求更高 |
| RDB + AOF | 两者同时开 | 生产最常见,AOF 保完整性,RDB 保恢复效率 |
2. RDB:快照型持久化
RDB 的核心思想是:
在某个时间点,把当前整个数据集序列化成一个二进制快照文件,比如
dump.rdb。
典型触发方式:
save 900 1save 300 10save 60 10000- 手动执行
SAVE/BGSAVE - 主从复制全量同步时生成 RDB
✅ 优点
- 文件紧凑,适合备份。
- 恢复速度快,直接把快照装入内存即可。
- 对运行时 I/O 压力相对可控。
⚠️ 缺点
- 快照之间的数据可能丢失。
fork子进程有成本,内存大时会抖一下。- 写流量高时,
copy-on-write会放大内存消耗。
3. AOF:日志型持久化
AOF 的核心思想是:
每当 Redis 执行一条会修改数据集的命令,就把这条命令追加到 AOF 文件里;重启时按顺序回放这些命令,恢复数据。
常见刷盘策略:
| 配置 | 含义 | 数据安全性 | 性能 |
|---|---|---|---|
appendfsync always |
每次写都刷盘 | 最高 | 最慢 |
appendfsync everysec |
每秒刷一次 | 较高,最多丢约 1 秒 | 常用平衡方案 |
appendfsync no |
由 OS 决定何时刷盘 | 最弱 | 最快 |
Redis 7.0 之后的补充
如果你面试的是较新的 Redis 版本,AOF 还可以多说一句:
- 自 Redis 7.0.0 起,官方使用 multi-part AOF。
- AOF 不再只是单个大文件,而是拆成:
base fileincremental filesmanifest
这会让 AOF rewrite 的切换更安全,也更容易管理增量变更。
4. RDB 和 AOF 怎么选?
📊 对比表
| 对比项 | RDB | AOF |
|---|---|---|
| 落盘方式 | 全量快照 | 增量日志 |
| 数据恢复速度 | 快 | 慢一些 |
| 文件大小 | 相对小 | 相对大 |
| 数据完整性 | 较弱 | 较强 |
| 对磁盘写压力 | 较低 | 较高 |
| 适合场景 | 备份、灾备 | 高可靠业务 |
✅ 生产建议
- 纯缓存场景:可以不持久化,或者只做 RDB。
- 一般业务场景 :推荐
RDB + AOF(everysec)。 - 极致可靠场景:AOF 更重要,但要接受更高 I/O 成本。
🔧 三、Redis 在生成 RDB 文件时如何处理请求?
这是高频考点,因为它考察的是:
- Redis 是否会阻塞?
- 快照期间数据一致性怎么保证?
- 为什么大实例做
BGSAVE会抖?
1. 先给结论
如果执行的是 BGSAVE,Redis 主进程通常还能继续处理客户端请求。
它的做法不是"主线程一边写快照一边处理请求",而是:
- 主进程
fork一个子进程 - 子进程负责把当前内存视图写成 RDB
- 主进程继续处理读写请求
但这里有两个重要补充:
fork本身会有短暂阻塞成本。- 快照期间如果主进程继续写数据,会触发 Copy-On-Write(COW),带来额外内存开销。
2. 处理流程图
dump.rdb RDB子进程 Redis主进程 Client dump.rdb RDB子进程 Redis主进程 Client 发起写请求/读请求 触发 BGSAVE fork 子进程 按 fork 时刻的内存视图写临时 RDB 继续处理新请求 新写请求触发 COW 写完临时文件 通知完成 原子替换旧 RDB 文件
3. 为什么还能继续处理请求?
因为 BGSAVE 的核心是 父子进程职责分离:
- 子进程:负责把"fork 那一刻"的数据集写入磁盘。
- 父进程:继续接收客户端请求。
这意味着:
- 读请求:可以继续处理。
- 写请求:也可以继续处理。
- 但写入发生在 fork 之后的那些变更,不属于本轮 RDB 快照内容。
这点非常关键。
RDB 快照记录的是"某一瞬间的内存状态",不是"快照持续期间不断变化的最终状态"。
4. 那新写入的数据会不会丢?
分情况看:
- 如果只开启 RDB,那么这些新写入的数据要等下一次快照才会被落盘。
- 如果同时开启了 AOF,那么这些写命令还会继续进入 AOF,因此通常能更完整保留。
5. Copy-On-Write 到底在干什么?
fork 完成后,父子进程最开始会共享同一批物理内存页。
当主进程收到新写请求时:
- 如果改动到了某个共享页
- OS 会复制这个页
- 父进程改自己的副本
- 子进程继续读旧页,保证快照一致
这就是 Copy-On-Write。
⚠️ 工程影响
如果你的 Redis:
- 内存很大
- 写入非常频繁
- 正在做
BGSAVE或BGREWRITEAOF
那么就会出现两类风险:
| 风险 | 原因 | 结果 |
|---|---|---|
| 短暂卡顿 | fork 要复制页表 |
RT 抖动、请求尖刺 |
| 内存放大 | COW 复制被修改的内存页 | 峰值内存暴涨,甚至 OOM |
所以生产环境里常说:
做快照不是没有成本,而是把成本从"长时间阻塞"换成了"短暂 fork + 可能的 COW 放大"。
6. SAVE 和 BGSAVE 的区别
| 命令 | 行为 | 是否阻塞 |
|---|---|---|
SAVE |
主线程直接执行快照 | 是,严重阻塞 |
BGSAVE |
fork 子进程后台生成 |
主流程基本不阻塞,但 fork 有代价 |
✅ 面试回答建议
如果被问"Redis 生成 RDB 时如何处理请求",推荐这样答:
Redis 通常通过
BGSAVE生成 RDB。主进程会先fork出子进程,由子进程把 fork 时刻的数据视图写入临时 RDB 文件,主进程继续处理读写请求。读请求不受影响,写请求也能继续执行;但写入修改不会进入当前这份快照,而是通过 Copy-On-Write 保证子进程看到的是一致快照。代价是fork有短暂阻塞,大实例和高写入场景下还可能出现 COW 带来的内存放大。
⏰ 四、Redis 数据过期后的删除策略是什么?
很多人会误以为:
key 到了 TTL,Redis 就会"立刻"删除。
其实不是。
Redis 为了避免把大量 CPU 时间都花在"扫过期 key"上,采用的是 惰性删除 + 定期删除 的组合策略。
1. 惰性删除(lazy expiration)
意思是:
- 当客户端访问某个 key 时
- Redis 先检查这个 key 有没有过期
- 如果已过期,就先删除,再返回不存在
优点
- 很省 CPU
- 只有访问到时才处理
缺点
- 如果某个已过期 key 之后再也没人访问
- 那它会继续占着内存一段时间
2. 定期删除(active expiration)
只靠惰性删除不够,因为很多过期 key 可能永远没人再访问。
所以 Redis 会周期性抽样检查带过期时间的 key:
- 随机抽一批带 TTL 的 key
- 删除其中已经过期的 key
- 如果过期比例很高,就继续扫一轮
这本质上是一个:
CPU 成本和内存回收效率之间的折中。
3. 为什么 Redis 不做"定时器精准删除"?
因为如果给每个 key 都挂一个独立定时任务:
- key 数量一大,调度成本非常高
- 时间轮 / 小顶堆也会引入额外内存和管理开销
- 高并发场景下会把 Redis 主线程拖慢
所以 Redis 选择了更实用的折中方案:
- 访问时顺手删
- 后台定期抽样删
4. 过期 key 在主从/AOF 中怎么处理?
这里是一个经常被忽略的细节。
为了保证复制一致性:
- 主节点负责"裁定"过期
- 当主节点确认某个 key 过期后,会合成一条
DEL - 这个
DEL会传播给 AOF 和副本
也就是说,Redis 不依赖主从时钟绝对一致来分别删除同一个 key,而是把删除动作尽量集中在主节点控制。
📊 过期删除策略总结表
| 策略 | 触发时机 | 优点 | 缺点 |
|---|---|---|---|
| 惰性删除 | key 被访问时 | CPU 友好 | 容易让过期 key 滞留内存 |
| 定期删除 | 后台周期扫描 | 能清理冷过期 key | 要消耗额外 CPU |
| 定时器删除 | 理论可行,Redis 不采用 | 删除及时 | 调度成本太高 |
✅ 面试标准答法
Redis 的过期删除不是单一策略,而是惰性删除和定期删除结合。惰性删除指访问 key 时发现过期就删除;定期删除指 Redis 周期性随机抽样带 TTL 的 key,把过期的清掉。这样做是为了在 CPU 开销和内存回收效率之间做平衡。
🗑️ 五、Redis 中有哪些内存淘汰策略?
1. 先区分"过期删除"和"内存淘汰"
这两个概念很容易混。
- 过期删除:key 自己到了 TTL。
- 内存淘汰 :Redis 达到
maxmemory后,不得不删一些 key 给新数据腾空间。
所以判断顺序通常是:
- 先看 key 有没有过期。
- 如果实例整体内存还不够,再触发淘汰策略。
2. 经典高频答案:8 种策略
如果你面对的是常规面试题,大多数场景回答下面这 8 种就够了:
| 策略 | 含义 |
|---|---|
noeviction |
不淘汰,写请求直接报错 |
volatile-lru |
只在设置了过期时间的 key 中淘汰最近最少使用的 |
volatile-lfu |
只在设置了过期时间的 key 中淘汰最不经常使用的 |
volatile-random |
只在设置了过期时间的 key 中随机淘汰 |
volatile-ttl |
只在设置了过期时间的 key 中优先淘汰剩余 TTL 更短的 |
allkeys-lru |
在所有 key 中淘汰最近最少使用的 |
allkeys-lfu |
在所有 key 中淘汰最不经常使用的 |
allkeys-random |
在所有 key 中随机淘汰 |
3. 如果按最新官方文档看:Redis 8.6 新增了 LRM
这里给你一个具体日期,方便你回答"最新版本"时不出错:
- Redis Open Source 8.6.0 发布于 2026 年 2 月。
- 这一版官方新增了两种 LRM(Least Recently Modified) 策略:
volatile-lrmallkeys-lrm
所以如果你问"截至 2026-04 Redis 官方有哪些淘汰策略",更完整的答案是:
| 分类 | 策略 |
|---|---|
| 不淘汰 | noeviction |
| 仅淘汰带 TTL 的 key | volatile-lru / volatile-lfu / volatile-random / volatile-ttl / volatile-lrm |
| 可淘汰所有 key | allkeys-lru / allkeys-lfu / allkeys-random / allkeys-lrm |
一个容易忽略的细节
LRU / LFU / LRM 在 Redis 里都不是"全量精确排序后再淘汰",而是基于采样的近似算法。
这意味着:
- Redis 不会为了找"全局最老 / 最不常用 / 最久未修改"的 key 而扫描全库
- 这样 CPU 成本更低
- 但淘汰结果是"足够好",不是"数学意义上的绝对最优"
4. LRU、LFU、LRM、TTL 分别适合什么场景?
| 策略族 | 核心思想 | 更适合什么业务 |
|---|---|---|
| LRU | 最近不用的先删 | 热点会快速变化的缓存 |
| LFU | 访问频率低的先删 | 热点稳定、长尾明显的缓存 |
| LRM | 最近很久没被修改的先删 | 读多写少,且"最近更新更重要"的业务 |
| TTL | 快过期的先删 | 生命周期已经由业务 TTL 明确表达 |
| Random | 随机删 | 测试或低要求场景 |
5. 生产里最常见的选择
场景 1:纯缓存
推荐:
conf
maxmemory 4gb
maxmemory-policy allkeys-lru
原因:
- 不依赖每个 key 都有 TTL
- 命中率通常比较稳
- 运维成本低
场景 2:很多 key 不允许被淘汰,只有临时 key 能删
推荐:
conf
maxmemory-policy volatile-lru
前提是:
- 你必须真的给"可淘汰 key"设置了 TTL
- 否则 Redis 可选的淘汰对象会很少,甚至无 key 可删
场景 3:热点相对稳定
推荐:
conf
maxmemory-policy allkeys-lfu
6. 一个容易踩坑的点
如果你设置的是:
conf
maxmemory-policy noeviction
那么当内存打满后:
- 读请求还能继续
- 但写请求会报错
这对"把 Redis 当数据库用"的业务尤其危险。
✅ 面试简答模板
Redis 的内存淘汰策略是在达到
maxmemory后触发的。经典面试题一般答 8 种:noeviction、volatile-lru、volatile-lfu、volatile-random、volatile-ttl、allkeys-lru、allkeys-lfu、allkeys-random。如果按最新官方版本看,截至 Redis 8.6.0(2026 年 2 月)还新增了volatile-lrm和allkeys-lrm。生产里最常见的是allkeys-lru或allkeys-lfu。
🧩 六、Redis 的内存碎片化是什么?如何优化?
1. 什么叫内存碎片化?
用最直白的话讲:
Redis 逻辑上已经释放了一部分内存,但操作系统层面看到的 RSS 还是很高,或者分配器手里的内存块不够整齐,导致这些内存没能高效返还给 OS,也没被后续分配很好复用。
所以我们会看到一种现象:
used_memory看起来没那么高- 但
used_memory_rss很高 mem_fragmentation_ratio也偏大
这就叫碎片化。
2. 为什么 Redis 容易出现碎片?
核心原因通常有这几个:
(1)频繁创建/删除不同大小的对象
比如:
- 大量短生命周期 key
- list / hash / zset 大小波动明显
- 批量写入后又批量删除
这会让分配器手里的内存块变得不规整。
(2)大 key 修改频繁
大对象一旦经常扩容、缩容、重写,内存页更容易出现空洞。
(3)fork + Copy-On-Write 的放大效应
在执行 BGSAVE / BGREWRITEAOF 时:
- Redis 会
fork - 写流量大时很多页被复制
- 峰值 RSS 往往比平时更高
(4)内存分配器行为
Redis 常配合 jemalloc 使用。
分配器为了性能会缓存、复用内存页,但这不等于它会立刻把空闲页还给操作系统。
3. 该看哪些指标?
📊 建议先看这几个
bash
redis-cli INFO memory
redis-cli MEMORY STATS
redis-cli MEMORY DOCTOR
重点关注:
| 指标 | 含义 | 怎么看 |
|---|---|---|
used_memory |
Redis 真实分配给数据结构的内存 | 逻辑数据量 |
used_memory_rss |
进程在 OS 层面占用的物理内存 | 真实 RSS |
mem_fragmentation_ratio |
used_memory_rss / used_memory |
粗粒度碎片比 |
allocator_frag_ratio |
分配器层面的真实外部碎片指标 | 更值得关注 |
allocator_rss_ratio |
分配器保留但未返还 OS 的程度 | 观察归还能力 |
active_defrag_running |
当前是否正在主动碎片整理 | 观察 defrag 是否生效 |
一个容易误判的细节
不要只看 mem_fragmentation_ratio 一个数。
因为官方文档也强调:
- 如果绝对字节量不大
- 即使 ratio 看起来偏高
- 也未必是大问题
所以你要同时看:
mem_fragmentation_ratiomem_fragmentation_bytesallocator_frag_ratioused_memory_peak
4. 如何优化内存碎片?
✅ 方法 1:开启 active defrag
这是 Redis 官方提供的主动碎片整理机制。
conf
activedefrag yes
如果要更细调,还可以配合这些参数:
conf
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
active-defrag-threshold-upper 100
active-defrag-cycle-min 1
active-defrag-cycle-max 25
active-defrag-max-scan-fields 1000
适用场景
- 长期运行实例
- key churn 很高
- 删除多、对象大小波动大
注意点
- 会额外占用 CPU
- 不是"越 aggressive 越好"
✅ 方法 2:做对象模型优化,减少 churn
比起事后整理,更推荐的做法是从数据模型上减碎片。
比如:
- 避免超大 key
- 避免频繁把一个大 value 整体覆盖
- 尽量使用更紧凑的数据结构
- 让 key 生命周期更均匀,不要突然海量创建、海量删除
✅ 方法 3:给持久化留足内存余量
这点非常重要。
如果你的 Redis 既要高写入,又要做 BGSAVE/BGREWRITEAOF,一定要预留足够内存。
原因是:
- 碎片化会抬高 RSS
fork+ COW 又会进一步放大峰值内存- 最终可能不是数据本身太大,而是碎片 + COW 把你打爆
所以 maxmemory 不要贴着机器物理内存上限设。
✅ 方法 4:必要时做 MEMORY PURGE
某些场景下,可以尝试:
bash
redis-cli MEMORY PURGE
它的作用更偏向于:
- 让分配器尝试把可归还的页返还给操作系统
- 适合删除高峰过后,RSS 长时间不下来的场景
但要注意:
- 它不是万能按钮
- 对所有碎片问题都不一定立竿见影
✅ 方法 5:在合适窗口做重启/迁移/主从切换
如果实例长期运行、碎片严重、业务允许短切换:
- 通过主从切换
- 滚动重启
- 迁移到新实例
往往是最直接的"碎片清零"手段。
📊 优化思路总结表
| 方案 | 本质 | 优点 | 代价 |
|---|---|---|---|
activedefrag |
在线整理碎片 | 不用停机 | 增加 CPU |
| 优化数据模型 | 从源头减少碎片 | 长期收益最好 | 需要改业务模型 |
MEMORY PURGE |
归还可释放页 | 操作简单 | 效果不一定稳定 |
| 重启/切换 | 直接重建内存布局 | 效果最明显 | 有运维窗口要求 |
| 预留内存余量 | 抵抗 COW 和 RSS 峰值 | 风险低 | 可用容量下降 |
✅ 面试回答建议
Redis 的内存碎片化,指的是 Redis 逻辑上释放了内存,但操作系统看到的 RSS 仍然较高,或者分配器内部出现大量不连续空洞,导致内存利用率下降。常见原因包括频繁创建删除不同大小对象、大 key 反复修改、
fork期间的 COW 放大,以及内存分配器本身的行为。优化方法包括开启activedefrag、优化 key/value 模型、控制大 key 和删除抖动、必要时执行MEMORY PURGE,以及在运维窗口通过重启或主从切换重建内存布局。
🧾 七、Redis 的虚拟内存(VM)机制是什么?
这一题现在如果直接按"现有功能"回答,就容易答错。
1. 先说正确版本结论
- Redis 曾经有过 VM(Virtual Memory)机制。
- 这个机制在 Redis 2.4 被标记为 deprecated。
- 在 Redis 2.6 被移除。
所以:
现代 Redis 面试里,VM 机制更多是一个历史知识点,而不是当前线上可用能力。
2. 它当年想解决什么问题?
它想解决的是:
内存不够,但又不想直接淘汰数据,能不能把冷数据先换到磁盘?
你可以把它理解成 Redis 早年的一种"值级别 swap"思路:
- key 的元信息尽量还留在内存
- value 比较大、又不常访问时
- 就把 value 换出到磁盘文件
- 真要访问这个 key,再从磁盘换回内存
3. 为什么后来被废弃?
看起来很美,但实践里问题很多:
(1)复杂度高
Redis 本来追求的是:
- 内存访问简单直接
- 单线程模型下低延迟
- 代码路径尽量短
VM 引入后:
- 要管理换入换出
- 要维护页、swap 文件、线程/阻塞行为
- 内部复杂度明显上升
(2)延迟不稳定
Redis 的价值之一是低延迟。
但一旦某次命中的是"被换出"的 value:
- 就必须从磁盘读回来
- 延迟波动会非常明显
这和 Redis 的设计目标其实是冲突的。
(3)收益不如预期
很多场景下,更简单有效的办法反而是:
- 加内存
- 做分片
- 设置 TTL
- 使用淘汰策略
- 把 Redis 只当缓存,不当全量存储
所以 VM 机制最终没有成为 Redis 的主路线。
4. 今天该怎么理解这道题?
如果面试官问你"Redis 的 VM 是什么",推荐你这样答:
Redis 早期曾提供过 Virtual Memory 机制,目标是在内存不足时把冷 value 换出到磁盘,访问时再换回,以此节省 RAM。它本质上类似于 Redis 内部做 value 级别的 swap。但这个机制在 Redis 2.4 被废弃、2.6 被移除,因为它会显著增加系统复杂度,并带来不可控的磁盘 I/O 延迟,不符合 Redis 追求高性能、低延迟的设计目标。现代 Redis 更常用的做法是通过 TTL、淘汰策略、持久化、分片扩容等方式治理内存。
⚠️ 八、这些问题背后真正的工程风险是什么?
很多人把这几个知识点分开背,但线上问题往往是连在一起爆的。
典型事故链路
实例写入增长
内存接近上限
触发淘汰/命中率下降
业务重建缓存导致更多写入
恰好触发 BGSAVE/BGREWRITEAOF
fork + COW 放大
RSS 飙升
碎片严重 / OOM / RT 抖动
所以真正的核心挑战在于:
- 你不能只会配置
maxmemory-policy - 还要知道持久化窗口、碎片、峰值内存、业务写流量之间会互相放大
✅ 更推荐的治理思路
- 先分清 Redis 的角色:到底是纯缓存,还是承担部分数据库职责。
- 给不同 key 设计清晰 TTL:不要让所有 key 无限期常驻。
- 合理设置
maxmemory和淘汰策略:别等打满才想起来。 - 关注 fork 成本:内存越大、页表越大、快照越容易抖。
- 监控碎片和峰值内存 :不是只看
used_memory。 - 避免大 key 和批量抖动删除:它们是碎片和延迟尖刺的常见源头。
📊 九、面试速查表
1. 六个问题的快速答案
| 问题 | 标准短答 |
|---|---|
| Redis 的持久化机制有哪些? | 主要是 RDB 和 AOF,生产常见是两者同时开启;纯缓存场景也可能关闭持久化。 |
| Redis 在生成 RDB 文件时如何处理请求? | 一般通过 BGSAVE,主进程 fork 子进程写 RDB,父进程继续处理请求;fork 有短暂阻塞,写入会触发 COW。 |
| Redis 数据过期后的删除策略是什么? | 惰性删除 + 定期删除。 |
| Redis 中有哪些内存淘汰策略? | 经典 8 种;如果按 Redis 8.6.0 官方,还新增 volatile-lrm 和 allkeys-lrm。 |
| Redis 的内存碎片化是什么?如何优化? | 逻辑内存下降但 RSS 仍高,通常由分配器、对象 churn、COW 等引起;可用 activedefrag、模型优化、MEMORY PURGE、重启切换等优化。 |
| Redis 的 VM 机制是什么? | 早期用于把冷 value 换出到磁盘的历史机制,2.4 废弃、2.6 移除。 |
2. 一段更像面试现场的话术
Redis 的内存治理可以分成三层:第一层是持久化,解决宕机恢复,主要是 RDB 和 AOF;第二层是过期删除和内存淘汰,解决内存满了怎么办,前者是惰性删除加定期删除,后者由
maxmemory-policy决定删谁;第三层是内存碎片治理,解决 RSS 偏高、fork风险大等问题,可以通过activedefrag和数据模型优化处理。至于 VM,是 Redis 早期把冷 value 换到磁盘的方案,但因为复杂度和延迟问题已经被废弃。
✅ 十、总结
这篇文章你真正要带走的,不是几个零散定义,而是下面这套理解框架:
- RDB:快照,恢复快,但会丢最近窗口数据。
- AOF:日志,更完整,但文件更大、恢复更慢。
- RDB 生成期间 :主进程还能处理请求,但
fork和 COW 是成本中心。 - 过期删除:不是立刻删,而是惰性删除 + 定期删除。
- 内存淘汰 :达到
maxmemory后才触发,核心在于"删谁最不伤命中率"。 - 内存碎片:不是数据变多,而是内存利用不整齐,RSS 会偏高。
- VM:历史方案,今天知道原理和废弃原因即可。
如果你是拿这篇文章准备面试,我建议你重点练会下面三句话:
- RDB 快照期间,Redis 为什么还能继续处理请求?
- 过期删除和内存淘汰到底有什么区别?
- 为什么 Redis 不是只看
used_memory,还要看 RSS 和碎片率?
把这三句讲顺了,Redis 这组题基本就不是死记硬背,而是真的理解了。
🔗 参考资料
- Redis 官方文档:Persistence
https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/ - Redis 官方文档:EXPIRE
https://redis.io/docs/latest/commands/expire/ - Redis 官方文档:Key eviction
https://redis.io/docs/latest/develop/reference/eviction/ - Redis 官方文档:INFO
https://redis.io/docs/latest/commands/info/ - Redis 官方文档:MEMORY STATS
https://redis.io/docs/latest/commands/memory-stats/ - Redis 官方文档:Virtual memory (deprecated)
https://redis.io/docs/latest/operate/oss_and_stack/reference/internals/internals-vm/ - Redis Open Source 8.6.0 release notes(2026 年 2 月)
https://redis.io/docs/latest/operate/oss_and_stack/stack-with-enterprise/release-notes/redisce/redisos-8.6-release-notes/ - Redis 8.6
redis.conf(active defrag 配置项)
https://raw.githubusercontent.com/redis/redis/8.6/redis.conf