← 返回最佳实践列表

*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 监控与告警。