看到命中率 60%,第一反应是"内存不够,加内存"——但加完内存命中率还是 60%。因为命中率低是四种完全不同的病,加内存只对其中一种有效。
第一步:先把命中率算对
很多人直接从监控面板读一个数字,但那个数字可能被重启清零了。
redis-cli INFO stats | grep keyspace
输出里两个关键值:
keyspace_hits:命中次数keyspace_misses:未命中次数
命中率 = hits / (hits + misses)。注意 misses 包含「故意查不存在的 key」,比如你缓存了空值标记 null,代码先查 DB 再写空标记,这一轮 miss 是正常的。
另外 Redis 重启后这两个计数器归零,监控面板如果在重启后没重新计算,数字会突然跳到 100% —— 这也是「命中率看起来正常但 DB 压力很大」的常见原因。
第二步:分清是「没查」还是「没写」
未命中只可能是两种情况:key 压根没写进去,或者写进去了但已经过期/被淘汰。
# 看剩余过期时间
redis-cli TTL <key>
| 返回值 | 含义 | 典型问题 |
|---|---|---|
| 大于 0 | 正常,还有 N 秒 | —— |
| -1 | key 存在但永不过期 | 缓存泄漏:越积越多,内存涨、命中率虚高 |
| -2 | key 不存在 | 没写进去 / 已被淘汰 / 已过期 |
-1 是很容易被忽略的坑:代码里写 set(key, value) 忘了设 TTL,数据永久驻留。这类 key 命中率很高,但内存只涨不降,最后触发淘汰把真正需要的热点 key 挤掉。
第三步:扫热 key 和大 key
缓存设计不合理,会同时出现「命中率低」和「内存高」两个症状。
# 扫描大 key(内存占用)
redis-cli --bigkeys
# 查看单个 key 占用
redis-cli MEMORY USAGE <key>
# 扫描热 key(需实例开启 LFU 淘汰策略才可用)
redis-cli --hotkeys
--hotkeys 只有在 maxmemory-policy 为 LFU 系列(如 allkeys-lfu)时才有意义,LRU 策略下会直接报错。如果线上是 LRU,改用 MONITOR 抽样更实际,但高 QPS 下 MONITOR 会拖慢实例,只能在低峰短时使用。
第四步:四种病的区分与处理
| 症状 | 病因 | 处理方向 |
|---|---|---|
| 大量 key 查不到,DB QPS 暴涨 | 缓存穿透(查不存在的数据) | 缓存空值 + 布隆过滤器前置 |
| 单个热点 key 突然过期,瞬间打满 DB | 缓存击穿 | 逻辑过期 + 互斥重建(singleflight) |
| 大批 key 同一时刻过期 | 缓存雪崩 | TTL 加随机抖动,分批预热 |
| 内存持续上涨、命中率高但不降 | 缓存泄漏 | 补 TTL、清理无效 key |
穿透和击穿的区别只在一句话:穿透是数据根本不存在(每次都要打到 DB),击穿是数据存在但缓存刚好失效(一瞬间大量并发去重建)。
常见坑
maxmemory-policy noeviction:内存到上限后写入直接报 OOM 错误,缓存场景应改成allkeys-lru。KEYS *排查:百万级 key 的实例上会阻塞主线程数秒,生产一律用SCAN游标遍历。volatile-lru的陷阱:它只在设置了过期时间的 key里淘汰,如果大部分 key 没有 TTL,内存满了会直接拒绝写入。- 缓存空值没设 TTL:穿透防护写成
set(key, null)却不设过期时间,等于把一次性脏查询永久固化下来。
相关速查
- Redis 缓存排查速查:键空间扫描、内存淘汰、慢查询命令
- Redis 命令速查:数据结构常用操作
- Linux 磁盘排查:内存/磁盘类排查思路可互相印证