*Redis 内存满了怎么办:OOM 排查、淘汰策略与扩容
Redis 内存满了可能表现为 OOM command not allowed、缓存键被大量淘汰、进程 RSS 持续增长,甚至实例被操作系统 OOM Killer 终止。处理时应先区分 maxmemory 限制与操作系统物理内存耗尽,再检查数据、过期键、客户端缓冲、复制积压和持久化开销,避免直接清空数据或盲目扩容。
*先判断是哪一种 OOM
应用常见错误:
OOM command not allowed when used memory > 'maxmemory'
这通常表示 Redis 已达到 maxmemory,当前淘汰策略无法为新写入释放足够空间。它不等于服务器物理内存已经完全耗尽。
如果进程突然退出、连接断开,系统日志出现 Out of memory: Killed process,则是操作系统层面的内存耗尽。两者需要不同的处理:前者重点检查数据与淘汰策略,后者还要检查 RSS、写时复制、其他进程和系统保留内存。
先收集只读信息:
redis-cli INFO memory
redis-cli INFO stats
redis-cli INFO clients
redis-cli INFO persistence
redis-cli INFO replication
redis-cli CONFIG GET maxmemory maxmemory-policy
重点字段:
| 字段 | 含义与用途 |
|---|---|
used_memory |
Redis 分配器报告的主要内存使用量,用于与 maxmemory 比较 |
used_memory_rss |
操作系统看到的驻留内存,接近真实物理占用 |
maxmemory |
Redis 数据使用上限;为 0 时的行为取决于版本和平台 |
maxmemory_policy |
达到上限后的淘汰或拒绝写入策略 |
evicted_keys |
因内存上限被淘汰的累计键数,应观察增量 |
mem_fragmentation_ratio |
RSS 与分配器使用量的比值线索,不能单独作为扩容依据 |
latest_fork_usec |
最近一次相关 fork 的耗时,辅助判断持久化压力 |
故障期间不要先执行 FLUSHALL、FLUSHDB 或重启。这些操作可能直接丢数据、丢失现场,并让恢复后的缓存集中回源。
*写入报 OOM 时的应急顺序
*1. 确认当前淘汰策略
redis-cli CONFIG GET maxmemory-policy
noeviction 不主动淘汰键。达到上限后,会导致新的写入或可能增加内存的命令返回错误,读操作通常仍可继续。纯缓存场景可评估 allkeys-lru 或 allkeys-lfu,但改变策略意味着允许 Redis 删除业务键,必须先确认所有键都可重建。
volatile-* 策略只从设置了过期时间的键中选择淘汰对象。如果大部分键没有 TTL,即使策略不是 noeviction,仍可能没有可淘汰对象而返回 OOM。
临时调整示例:
redis-cli CONFIG SET maxmemory-policy allkeys-lru
这应是经过业务确认的止损动作,不是通用修复。永久生效还需更新配置管理,并监控淘汰速率和缓存命中率。
*2. 停止异常写入和无限增长的数据
检查最近发布、批量任务、消息积压和键数量增长。常见原因包括:
- 临时键没有设置 TTL,或 TTL 被后续写入意外移除。
- List、Stream、Set 等集合只追加不清理。
- 缓存键拼接了随机参数,产生大量无法复用的键。
- 大对象写入增加,单个集合或字符串持续膨胀。
- 业务重试把同一任务以不同键重复写入。
使用游标分批检查,不要在故障时执行 KEYS *:
redis-cli SCAN 0 MATCH "cache:temp:*" COUNT 200
redis-cli --bigkeys
redis-cli MEMORY USAGE cache:large-object SAMPLES 100
确认数据可以删除后,按明确前缀和业务边界限速清理。大对象可评估 UNLINK,但异步释放不代表遍历、命令处理和网络完全没有成本。
*3. 为恢复写入预留真实容量
提高 maxmemory 只能调整 Redis 内部阈值,不会增加机器内存。如果 RSS 已接近物理上限,继续提高阈值可能让操作系统直接终止进程。
扩容前应给以下部分预留空间:
- 数据集本身和内部对象开销。
- 客户端输入、输出缓冲区。
- 复制积压缓冲区及全量同步期间的复制相关缓冲。
- AOF 缓冲与重写开销。
- RDB/AOF 重写期间
fork后的写时复制页面。 - 内存碎片、操作系统和其他进程。
在无法快速确认原因时,优先限制非关键写入、扩展真实可用内存或迁移到容量合适的实例。仅修改一个数字可能把可恢复的写入错误变成整机故障。
*键不多为什么内存仍然很高
*客户端输出缓冲区积压
慢消费者、阻塞 Pub/Sub 订阅者或超大返回值可能让输出缓冲持续增长:
redis-cli INFO clients
redis-cli CLIENT LIST
关注 obl、omem 等输出缓冲字段,并结合具体版本文档理解单位与限制。不要根据单个连接瞬间状态直接断开全部客户端。应先定位异常调用方,再评估 client-output-buffer-limit、消费速度和返回数据量。
*复制与全量同步
副本断开、复制延迟或全量同步可能增加复制相关缓冲。检查:
redis-cli INFO replication
主节点需要关注副本状态和复制积压,副本需要关注主节点连接状态与偏移进度。读写分离还会引入旧值问题,不能把复制延迟简单当成内存问题处理。
*过期键尚未被回收
Redis 结合惰性过期和主动过期机制删除键。键到期不代表同一时刻物理内存立即下降;未被访问的过期键可能等待主动清理,已释放内存也不一定马上归还操作系统。
检查 INFO keyspace 中各数据库的键数与带过期时间键数,并观察 expired_keys 的增量。不要仅因内存没有立即下降就反复重启实例。
*内存碎片与分配器行为
redis-cli MEMORY STATS
redis-cli MEMORY DOCTOR
mem_fragmentation_ratio 高不总是需要立即处理,低也不一定代表内存充足。应结合 RSS 趋势、分配器、版本、负载和重启成本判断。主动碎片整理会消耗 CPU,启用前需要压测,详见内存碎片治理。
*正确选择淘汰策略
| 策略类别 | 淘汰范围 | 适用条件 |
|---|---|---|
noeviction |
不淘汰,内存不足时拒绝相关写入 | 数据不能由 Redis 自行删除,或应用必须明确处理容量不足 |
allkeys-lru / allkeys-lfu |
所有键 | 所有数据都可丢失或重建的纯缓存 |
volatile-lru / volatile-lfu |
仅带 TTL 的键 | 可淘汰缓存都有 TTL,并与其他数据明确区分 |
allkeys-random / volatile-random |
随机选择对应范围内的键 | 访问模式不适合近似 LRU/LFU,且接受随机淘汰 |
volatile-ttl |
优先选择剩余 TTL 较短的键 | 业务明确希望按剩余生命周期淘汰 |
LRU 关注最近使用,LFU 关注访问频率。Redis 的实现是近似算法,不应把它理解为严格排序。选择策略时应使用真实访问分布测试命中率,而不是只看配置名称。
持久数据与可淘汰缓存混在同一实例时,allkeys-* 风险尤其高。更稳妥的方式是分离实例或数据库边界,并明确每类数据的持久化和容量策略。
*如何预防再次发生
- 为每类键定义生命周期,写入和更新时检查 TTL 是否被意外清除。
- 为 List、Stream 和集合设置长度或保留周期,避免只增不减。
- 按业务前缀统计键数、内存和增速,发现异常键生成逻辑。
- 监控
used_memory、RSS、evicted_keys、拒绝写入和客户端缓冲的增量。 - 将淘汰速率与缓存命中率一起告警,淘汰增加不只是一条容量信息,也可能意味着回源风暴。
- 定期演练 RDB/AOF 恢复,并测量恢复期间内存需求。
- 在写入高峰和持久化同时发生时压测,验证写时复制预留是否足够。
- 扩容后更新告警阈值和配置管理,避免只修改运行中实例。
*常见问题
*设置 allkeys-lru 后就不会再报 OOM 吗
不一定。没有足够可释放对象、缓冲区占用异常、写入速度超过回收能力或策略配置未按预期生效时,仍可能出现问题。它也无法防止操作系统物理内存耗尽。
*直接重启能不能释放内存
重启可能暂时降低 RSS,但会丢失未持久化数据、造成服务中断,并带来加载数据和缓存回源压力。没有确认原因前,重启往往只是推迟下一次故障。
*maxmemory 应该设置为物理内存的多少
没有通用百分比。数据集、写入速率、持久化、复制、客户端数量、容器限制和宿主机其他进程都会影响预留。应通过真实负载测量峰值 RSS,并保留故障与写时复制余量。
相关阅读:过期策略与内存淘汰机制、大 Key 与热 Key 排查、RDB 与 AOF 持久化实践、Redis 监控与告警。