allkeys-lru未起作用却报OOM,根本原因不是淘汰失效,而是Redis预估单key释放内存时仅计value和编码开销,忽略key元信息overhead;当存在上千万小key(如user:123:status),每个overhead达200B、value仅10B,删一个仅腾10B,无法及时应对突发写入或client-output-buffer堆积。

为什么allkeys-lru没起作用,却报OOM
不是淘汰策略失效,而是Redis在执行淘汰前会预估「删一个key能净释放多少内存」,这个估算只包含value和编码开销,**不减去key本身的元信息overhead**。当你有上千万个小key(比如user:123:status),每个overhead 200B、value才10B,Redis认为删一个只能腾10B——它得连续删10万个才能凑够1MB,但淘汰动作是渐进式触发的,来不及应对突发写入或缓冲区堆积。
怎么确认是client-output-buffer导致OOM
别只看used_memory,重点查三组指标:
-
redis-cli info memory | grep -E "used_memory|overhead|mem_fragmentation_ratio":若used_memory_overhead/used_memory> 0.4,且mem_fragmentation_ratio在1.0–1.5之间,说明overhead吃掉了弹性空间 -
redis-cli info clients | grep -E "client_longest_output_list|client_biggest_input_buf":任一值持续 > 1MB,说明有客户端卡住,obuf在膨胀 -
redis-cli client list | grep -E "omem=|cmd=" | awk '$5 ~ /omem=[0-9]+/ {print $5, $13}' | sort -k2 -nr | head -10:直接列出omem最大的连接,重点关注cmd=subscribe或空命令的条目
client-output-buffer-limit调小反而更危险
默认client-output-buffer-limit pubsub 32mb 8mb 60,看似宽松,但断连前缓冲区已占满;若改成8mb 2mb 30,会更早踢人——可一旦客户端没实现退避重连,就会立刻重连+重订阅,旧obuf还没释放,新obuf又堆起来,形成“踢-连-积-踢”循环,瞬时内存尖峰比原来还高。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
真正有效的做法是:
- 用
CLIENT SETNAME给关键客户端打标(如monitor-collector),便于快速识别来源 - 对慢消费的pub/sub客户端,加一层代理做消息批处理或限速,而不是靠Redis硬扛
- 检查业务代码里是否用了
MONITOR、KEYS *等命令——它们会把全量响应塞进单个client的obuf
哪些场景最容易让obuf失控
obuf不是均匀增长的,它集中在几类高风险操作上:
- Pub/Sub订阅者消费延迟 ≥1秒,每秒发布10KB消息 → 10秒后单个client就积压100KB;1000个这样的订阅者 = 至少额外占用100MB
- 使用
redis-cli --bigkeys时,如果输出未被及时读取(比如管道阻塞或终端卡顿),响应数据全堆在obuf里 - 监控采集端用
INFO+KEYS *轮询,每次返回几万行,obuf瞬间飙到几MB -
CLIENT LIST结果中omem> 512KB 且cmd为空,大概率是客户端挂起、没读响应
overhead和obuf都不受maxmemory限制,它们能直接把Redis推到OOM边缘,而你从used_memory里完全看不出问题。定位时必须跳过“淘汰策略是否生效”的思维定式,先盯住omem和overhead这两块“隐形内存”。

















