Redis List 做消息队列必然导致内存耗尽,因其无消费位点、无自动清理、内存开销达逻辑大小3–5倍,且未配maxmemory或淘汰策略时会触发OOM;必须配置maxmemory、allkeys-lru策略和足够samples,并配合LTRIM、BLPOP超时及内存监控。

Redis List 做消息队列时,积压直接吃光内存,不是“可能”,而是“必然”——只要没配 maxmemory 或淘汰策略不对,100 万条 2KB 的消息就能干掉 2GB 内存,触发 OOM command not allowed when used memory > 'maxmemory' 报错,甚至被系统 OOM Killer 杀进程。
为什么 List 积压比 Stream 更容易爆内存
List 没有消费位点、没有自动清理机制,LPOP/RPOP 是“取完即删”,但一旦消费者挂了或处理慢,消息就堆在 list 里不动;而 Redis 对 list 的每个元素都额外套一层 robj + SDS + dictEntry,小消息实际内存开销是逻辑大小的 3–5 倍。更麻烦的是,LRANGE 查看积压时还会临时分配缓冲区,进一步推高 used_memory_rss。
- 单条消息 1KB,10 万条实际占内存常超 300MB(不是 100MB)
-
BLPOP阻塞读不会释放内存,只等数据来,不等消费者上线 - 没设
maxmemory时,Redis 会一直写,直到操作系统杀进程
必须配的三个配置项:maxmemory、policy、sample
仅设 maxmemory 不够,还得配对的淘汰策略,否则到上限就直接拒绝写入——这对消息队列等于丢消息。List 场景下推荐用 allkeys-lru 或 allkeys-lfu,而不是默认的 noeviction。
-
maxmemory 2gb:建议设为机器总内存的 40%–60%,预留足够给复制缓冲、AOF 重写和系统页缓存 -
maxmemory-policy allkeys-lru:List 消息无过期时间,volatile-*类策略完全无效 -
maxmemory-samples 10:采样数太低(默认 5)会导致 LRU 误判,积压多时优先淘汰错的 key
用 BLPOP + 超时 + 限长控制写入节奏
靠消费者“自觉消费”不现实。得从生产端卡住入口:限制 list 长度、加超时、主动丢弃老消息。
- 写入前先
LLEN queue:task,超阈值(如 10000)就LTRIM queue:task 0 9999截断,保留最新 - 消费者必须用
BLPOP queue:task 30,超时后检查自身健康状态,避免假死 - 不要用
LPUSH+BRPOP组合,BRPOP在 list 为空时阻塞,但没人清空时它永远等不到
监控和巡检不能靠 guess,得盯这几个指标
Redis 自身不提供队列堆积告警,你得自己定时抓 INFO memory 和 LLEN,重点看三组数字是否同步上涨:
-
used_memory和LLEN queue:xxx同步涨 → 真实积压,要查消费者日志 -
mem_fragmentation_ratio > 1.5且used_memory不高 → 不是数据问题,是碎片或 fork 开销,别急着删 key -
evicted_keys持续增长 → 淘汰策略生效中,但注意:List 元素被 LRU 淘汰后,整个 list 对象可能还残留指针,实际释放不干净
最危险的盲区是认为“list 长度不大就没问题”——一个大 LPUSH 进去的 50MB 日志字符串,LLEN 显示长度是 1,但内存已暴涨。所以 memory usage queue:xxx 得定期跑,别只信 LLEN。











