看到命中率 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) 却不设过期时间,等于把一次性脏查询永久固化下来。

相关速查