导语:应用突然大量报错,日志里写着
OOM command not allowed when used memory > 'maxmemory',或者ERR max number of clients reached。登上去redis-cli敲什么都超时------这是 Redis 运维最常见的两种"猝死"。别急着重启,按下面四步走,先定位再处置。
一、第 1 步:先分清是"内存满"还是"连接满"
两种故障的排查路径完全不同,第一步必须分开看。
yaml
redis-cli -h 10.0.0.10 -p 6379
# 内存视图
INFO memory
# used_memory_human 已用内存(数据集+开销)
# used_memory_rss_human 操作系统看到的常驻内存
# maxmemory_human 配置的上限
# maxmemory_policy 淘汰策略
# mem_fragmentation_ratio 碎片率
bash
# 连接视图
INFO clients
# connected_clients 当前连接数
# blocked_clients 阻塞在 BLPOP 等的连接
# client_recent_max_input_buffer / output_buffer
INFO stats
# rejected_connections 因超 maxclients 被拒的连接数
# evicted_keys 因内存淘汰被删的 key 数
# expired_keys 已过期删除的 key 数
判读口径:
used_memory顶到maxmemory且evicted_keys猛涨 → 内存问题。connected_clients逼近maxclients、rejected_connections不为 0 → 连接问题。mem_fragmentation_ratio> 1.5 → 碎片严重;< 1 → 已经在用 swap,性能会断崖式下跌。
避坑 1 :maxmemory 设了但没设 maxmemory-policy,默认是 noeviction------写请求会直接报 OOM,而不是淘汰旧数据。这是"内存没满却写不进去"的头号原因。
二、第 2 步:找出谁在吃内存(大 key / 热点 key)
内存问题 90% 出在少数几个大 key 上。
bash
# 按数据类型采样,找出每种类型最大的 key(Redis 自带,开销低)
redis-cli --bigkeys
# Redis 6.0+ 还可以按内存排序
redis-cli --memkeys
# 精确定位某个 key 的真实内存占用(含内部编码开销)
redis-cli MEMORY USAGE my:big:hash SAMPLES 0
找不到方向时,用 SCAN 分批采样统计,而不是 KEYS *:
vbnet
# 抽样 1000 个 key,按前缀聚合数量,找"数量异常多"的业务前缀
redis-cli --scan --pattern 'order:*' --count 1000 | head -n 1000
# 找出大 hash / 大 zset 的元素个数
redis-cli HLEN order:detail:20261009001
redis-cli ZCARD rank:hot:global
热点 key 用 LFU 策略统计(需 maxmemory-policy 为 allkeys-lfu / volatile-lfu):
css
redis-cli --hotkeys
避坑 2 :KEYS *、FLUSHALL、MONITOR 在生产环境一律禁用。前两个会阻塞主线程导致秒级卡死,MONITOR 会把所有命令广播给慢客户端,几分钟就能把内存打爆。
避坑 3 :--bigkeys 内部仍要遍历全库,请在业务低峰期执行;超大实例可先 --scan 估量。
三、第 3 步:看清内存到底花在哪
used_memory 不等于数据集,开销同样能吃人。
bash
INFO memory
# used_memory_dataset 纯数据占用
# used_memory_overhead 客户端缓冲区、复制积压等开销
# used_memory_peak_human 历史峰值
# used_memory_lua 脚本
排查三个常被忽略的"隐形内存":
shell
# 1) 客户端输出缓冲区:慢客户端(订阅、MONITOR、大结果集)会积压
redis-cli CLIENT LIST
# 关注 omem(输出缓冲区)、qbuf、idle 三列,omem 特别大的连接就是"慢客户端"
# 2) 主从复制积压区
INFO replication
# repl_backlog_size 配置过大 / 从库长时间断连都会撑大内存
# 3) 过期 key 没被及时回收
INFO keyspace
# expires 与 keys 的差值异常,说明大量 key 无 TTL
四、第 4 步:常见根因与处置
排查到最后,逃不出这五类:
- 缓存不设 TTL:业务写入的 key 永不过期,只增不减。给所有缓存 key 加过期时间,或改用带容量的本地缓存 + Redis 组合。
- 大 key:单个 hash/list/zset 存了几十万甚至上百万元素(如全量字典、排行榜、日志队列)。按业务维度拆分,或用 hash tag 分片。
- 客户端连接泄漏 :应用没复用连接池、异常分支忘了
close(),connected_clients缓慢爬升。常见于短连接模式与定时任务。 - 输出缓冲区失控 :慢客户端订阅大频道、执行
MONITOR、KEYS,omem飙到几百 MB。 - AOF rewrite / fork 抖动 :重写期间 COW 会额外占用内存,
maxmemory没留余量就会触发 OOM。
应急止血:先降载,别急着删
ini
# 1) 临时改为 LRU 淘汰,先让写入活下来(注意会丢数据)
redis-cli CONFIG SET maxmemory-policy allkeys-lru
# 2) 删大 key 用 UNLINK(异步回收,不阻塞主线程),别用 DEL
redis-cli UNLINK order:detail:20260101
# 3) 批次删除,别一次删一堆
redis-cli --scan --pattern 'temp:*' --count 100 | xargs -L 100 redis-cli UNLINK
# 4) 断开空闲连接,清理连接泄漏
redis-cli CLIENT LIST | awk -F'idle=' '{split($2,a," "); if (a[1] > 3600) print $1}' | head
redis-cli CLIENT KILL ID <id>
长期治理:为实例设定 maxmemory(物理内存的 60%--75%,留出 fork 与 OS 空间)、显式配置淘汰策略、给所有缓存 key 加 TTL、把 used_memory、connected_clients、evicted_keys、rejected_connections 纳入监控告警(内存 > 80% 阈值、连接数 > 80% maxclients 提前预警)。
五、避坑清单
KEYS */FLUSHALL/MONITOR生产禁用,改用SCAN与INFO。- 删大 key 用
UNLINK而非DEL,DEL会阻塞主线程。 - 只设
maxmemory不设maxmemory-policy,默认noeviction会直接拒绝写入。 maxmemory不要设成物理内存全量:RDB 保存 / AOF 重写需要 fork,COW 要有余量。- 主从实例的
maxmemory应保持一致,从库更小会导致主从数据不一致。 maxclients受 OS 的ulimit -n限制,连接打满时先确认系统句柄数。- 上线前做一次容量基线:单 key 元素上限、平均 value 大小、日均 key 增量,出问题才能判断"现在算不算异常"。
小结 :顺序记成一句话------INFO 分清内存/连接 → --bigkeys 揪大 key → INFO memory 看清开销构成 → 按五类根因处置,先 UNLINK 降载再治理 。Redis 排查的关键是"先看指标、再找 key、后动数据":不要一上来就重启或
FLUSHALL,把"内存涨了多少、是谁在涨、涨在数据集还是开销"三个问题回答清楚,元凶自然现形。