Redis大Key排查必须用MEMORY USAGE配合SCAN遍历,因--bigkeys仅统计逻辑大小(如元素个数、字符串长度),无法反映真实内存占用;其与info memory差异源于Redis内部编码机制(如embstr/raw、ziplist/hashtable切换)导致内存开销倍增。

redis-cli --bigkeys 不能直接扫描出内存占用最大的 Key,它只返回逻辑大小(如字符串长度、集合元素个数)最大的 Key,和真实内存占用可能差几倍甚至几十倍。 想定位真正吃内存的 Key,必须绕过 --bigkeys,用 MEMORY USAGE 配合 SCAN 自行遍历。
为什么 --bigkeys 显示的“最大”和 info memory 对不上
这是最常被忽略的前提:Redis 内部编码机制导致「逻辑大小」≠「内存占用」。比如:
- 一个
STRING类型的 key,STRLEN返回 1024 字节,但 value 是 protobuf 序列化后的二进制 blob,实际MEMORY USAGE可能高达 8MB(含 redisObject 头、SDS 开销、对齐填充) - 一个
HASH有 50 个 field,HLEN是 50,但每个 value 都是 100KB 的 JSON,--bigkeys会把它当成小 key 漏掉 - 小字符串用
embstr编码,大字符串转raw;Hash 小时用ziplist,大了升成hashtable——内存翻倍就发生在临界点上,而--bigkeys完全感知不到
如何用 SCAN + MEMORY USAGE 真实定位大内存 Key
核心思路是:用 SCAN 游标分批拉 key,对每个 key 调用 MEMORY USAGE,记录超过阈值(如 1MB)的结果。示例脚本逻辑如下(Python):
import redis
r = redis.Redis(host='10.186.62.56', port=6382, password='xxx', decode_responses=False)
cursor = 0
big_keys = []
while True:
cursor, keys = r.scan(cursor=cursor, count=1000)
for key in keys:
try:
mem = r.memory_usage(key, samples=0) # samples=0 表示精确估算(Redis 4.0+)
if mem > 1024 * 1024: # 超过 1MB
big_keys.append((key, mem))
except Exception as e:
pass # key 可能已过期或被删
if cursor == 0:
break
for k, m in sorted(big_keys, key=lambda x: x[1], reverse=True)[:10]:
print(f"{k} -> {m} bytes")- 务必在从节点或业务低峰期执行,避免主节点 CPU 打满
-
count=1000是平衡速度与压力的经验值,太大会加重单次 SCAN 压力,太小则网络往返多 -
samples=0启用精确估算(默认是采样),但代价是更耗 CPU;生产环境可先用samples=10快速过一遍 - 脚本里要加异常捕获——
MEMORY USAGE对已过期但未被回收的 key 会报错
扫完之后怎么清理才不卡主节点
发现大 key 后,别直接 DEL,尤其是 LIST/HASH/ZSET 类型,删除过程会阻塞主线程。正确做法是:
- 优先用
UNLINK替代DEL,它把释放内存的操作放到后台线程异步执行 - 对超大
LIST或HASH,用LTRIM/HSCAN + HDEL分批删,每次不超过 1000 个元素 - 如果是业务可弃用的大 key(如日志快照、临时计算结果),加 TTL 后再删,避免误删正在读的缓存
- 集群环境下,确认该 key 所在 slot 是否集中在某个分片——如果只有一两个节点内存高,说明大 key 分布不均,需要重哈希或拆分
真正难的不是找到大 key,而是判断它是否还能删、删了会不会影响业务、以及删完后内存是否真能回落(比如有些 key 被其他服务反复重建)。所以每次操作前,先用 OBJECT REFCOUNT 和 OBJECT FREQ 看下引用计数和 LRU 频次,比盲目清理更重要。


















