Redis 客户端收到 OOM command not allowed when used memory > 'maxmemory' 时,首先要分清这是 Redis 的逻辑内存上限,还是宿主机或容器真的发生了 OOM kill。两者虽然都出现“OOM”,证据链和修复方式完全不同。前者可能保持进程存活、读取正常、旧键不变,只拒绝会继续增加内存的写命令;后者通常伴随进程退出、重启或内核与 cgroup 的杀进程记录。
适用范围与核心结论
本文适用于 Redis 配置了 maxmemory,写入返回内存上限错误,但服务仍能响应 PING、读取或监控命令的场景。V2CE 使用固定版本 Redis 7.4.10 Alpine 镜像、Docker 24.0.7、2 MiB maxmemory 与 noeviction 复现。
实验中前 49 个 16 KiB 值写入成功,第 50 次写入被拒绝;此时 Redis 进程仍运行,数据库保留 49 个键,evicted_keys=0。经过明确批准把上限调整为 4 MiB 后,新写入恢复为 OK,策略仍是 noeviction。这证明故障来自 Redis 配置上限,而不是容器进程死亡。
第一步:确认进程是否存活,避免把两种 OOM 混为一谈
先从服务管理层和 Redis 协议层分别取证。Docker 的 OOMKilled=true、退出码 137、cgroup memory.events 中的 oom_kill,支持容器或宿主机 OOM 判断;而 Redis 仍能执行 PING 和 INFO,更符合逻辑上限触发。
redis-cli PING
redis-cli INFO server
redis-cli INFO memory
redis-cli INFO stats
# Docker 部署同时检查进程层
docker inspect --format \
'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}}' redis
不要只看应用异常中的“OOM”字符串,也不要立即重启 Redis。重启可能短暂释放临时内存或清空无持久化实验数据,却会丢失触发时的关键证据,并不能解释容量规划、过期策略或工作负载为什么越界。
第二步:读取真实上限、策略与内存组成
从故障实例读取生效配置,不要只看仓库里的模板。再把 used_memory、maxmemory、数据集、复制/AOF 缓冲和碎片信息放在一起判断:
redis-cli CONFIG GET maxmemory maxmemory-policy
redis-cli INFO memory
redis-cli INFO stats
redis-cli DBSIZE
maxmemory-policy=noeviction 表示达到上限后不通过删除现有键腾出空间,而是让会增加内存的命令返回错误;读取现有数据通常仍可继续。它与客户端驱逐、操作系统 swap、容器内存上限不是同一个机制。used_memory_rss 也可能因分配器和碎片高于 used_memory,不能仅用 RSS 是否等于 maxmemory 判断策略是否生效。
第三步:判断是谁让数据集接近上限
检查键数量、过期键比例、键空间增长速度、写入速率和大键分布。生产环境不要直接运行无边界的 KEYS *;应使用有节制的采样、现有监控或经过审批的扫描工具。需要回答:
- 增长是预期业务容量,还是某类键缺失 TTL、键名维度爆炸或异常重试;
- Redis 承担的是不能自动删除的主数据,还是允许淘汰的缓存;
- 复制、AOF、客户端缓冲和 Lua/函数是否占用额外内存;
- 宿主机或容器是否仍有安全余量,能否承担更高上限及故障切换副本。
若应用把写入错误无限重试,会在 Redis 之外形成请求堆积。应同时检查客户端重试次数、退避、熔断和写入幂等性,避免容量故障扩散到线程池与消息队列。
第四步:选择与数据语义一致的修复
修复选项不能只按“最快恢复写入”选择。如果键不允许丢失,保留 noeviction,优先修正数据生命周期、清理经过确认的无效数据,或在宿主机有余量且完成容量评估后提高 maxmemory。如果这是可重建缓存,可以评估 allkeys-lru、allkeys-lfu 或 volatile 系列策略,但切换策略意味着允许 Redis 删除键,必须由业务所有者确认缓存未命中和数据一致性后果。
临时使用 CONFIG SET 可以验证容量调整是否有效,但运行时变更未必写回部署声明或配置文件。容器重建、主从切换或重启后可能恢复旧值。最终配置必须进入实际配置源、镜像、Helm/Compose 或配置管理系统,并在所有节点核对。
最小变更、监控与持久化
若容量评估确认可以提高上限,先记录旧值、实例可用内存、容器限制、复制拓扑和回滚阈值,再做小步调整:
# 保存旧值与关键状态
redis-cli CONFIG GET maxmemory maxmemory-policy
redis-cli INFO memory
redis-cli INFO replication
# 示例值不是生产建议;必须按已批准容量替换
redis-cli CONFIG SET maxmemory 4gb
# 验证写入、读取、拒绝错误和内存余量
redis-cli CONFIG GET maxmemory maxmemory-policy
redis-cli INFO memory
redis-cli INFO stats
监控至少覆盖 used_memory/maxmemory 比例、total_error_replies、evicted_keys、命令延迟、连接数、复制延迟和宿主机/cgroup 内存。告警应早于 100%,给数据清理或扩容留下窗口。对主从或集群环境,要确认每个节点的角色、上限和可用内存,避免只修主节点。
复核与回滚
复核时使用一条可识别、可清理的测试键验证写入,再读取确认;同时观察错误计数是否停止增长、旧键是否仍存在、策略是否未被意外改变、宿主机余量是否安全。不要用批量生产写入作为测试。
若提高上限导致宿主机接近压力线、复制延迟增大或容器面临 OOM kill,应把上限恢复为记录的旧值,并停止非必要写入或启用业务降级。若切换过淘汰策略,回滚策略不能恢复已经被驱逐的数据;因此策略变更前必须确认备份、可重建性和未命中影响。
V2CE 复现实验记录
隔离实例启动时配置 maxmemory=2097152 和 maxmemory-policy=noeviction,关闭持久化。脚本连续写入 16 KiB 固定值,前 49 次返回 OK,第 50 次返回 OOM command not allowed when used memory > 'maxmemory'。此时 used_memory_human=1.95M、DBSIZE=49、evicted_keys=0,容器状态仍为 running。
随后只把上限调整为 4 MiB,测试键写入恢复 OK;复核显示 maxmemory=4194304、策略仍为 noeviction、evicted_keys=0。实验结束后 Compose 自动删除容器和卷。原始配置、每次写入结果、INFO 输出与容器状态保存在 labs/redis-maxmemory-noeviction/evidence。