Redis 大 key 与热 key 排查实战:从发现到根治

Redis 大 key 与热 key 是线上缓存性能塌方的两大隐形杀手:前者悄悄撑爆内存、让删除操作阻塞整条主线程,后者把流量集中压到单节点,造成集群层面「一核有难、八核围观」。本文基于真实排雷经验,手把手教你用 redis-cli、MEMORY 命令与采样脚本定位这两类问题,并给出可落地的根治方案。

一、为什么大 key 和热 key 是缓存塌方的元凶

很多团队在学完 Redis 缓存三大问题(穿透、击穿、雪崩)的防护 后,以为缓存就稳了,结果大促前夜被一个 80MB 的 Hash 打挂。原因很简单:大 key 和热 key 不走「有没有命中」那套逻辑,它们直接攻击 Redis 的单线程模型——内存占用、网络带宽、命令耗时全部被单一 key 绑架。

深入理解单线程模型很关键:Redis 处理命令是串行的,一个 80MB 的 Hash 做 HGETALL 或 DEL,会在这几毫秒到几百毫秒内独占主线程,期间所有其他命令排队等待——这正是「大 key 让人感觉整站变慢」的底层原因。热 key 同理,海量 GET 打在同一分片,CPU 被单核吃满,集群其余节点却在围观。两者都源于「热点集中」,只是一个在体积、一个在频率。

二、大 key:如何不动声色拖垮集群

2.1 大 key 的典型形态

常见形态有四类:一个塞了几十万成员的 Hash/Set/ZSet;一条长度过万的 List;一个好几 MB 的 String(比如把整个页面 HTML 塞进一个 key);以及用 JSON 字符串当「大杂烩」容器。它们共同点是删除或序列化时极慢,而 Redis 删除大 key 默认是同步的,会阻塞主线程。

2.2 用 redis-cli –bigkeys 快速扫描

最省事的第一步,是让 Redis 自己在后台采样找出各类数据的最大 key:

# 被动采样,不会真遍历全库,对线上相对安全
redis-cli -h 10.0.0.12 -p 6379 --bigkeys

# 输出示例
# [00.00%] Biggest string found so far 'page:home' with 12 bytes
# [100.00%] Biggest hash   found so far 'user:feed:8872' with 412330 fields
# -------- summary -------
# Biggest   hash    found 'user:feed:8872' has 412330 fields

它只报告「每种类型最大的那个」,精度有限,但胜在零成本、能立刻暴露明显的问题 key。

2.3 精准定位:MEMORY USAGE 与 DEBUG OBJECT

拿到嫌疑 key 后,用 MEMORY USAGE 测真实字节数(O(1),线上可用),用 DEBUG OBJECT 看序列化长度与剩余 TTL:

redis-cli -h 10.0.0.12 -p 6379 MEMORY USAGE user:feed:8872
(integer) 88421345          # 约 84MB,典型大 key

redis-cli -h 10.0.0.12 -p 6379 DEBUG OBJECT user:feed:8872
# Value at:0 refcount:1 encoding:hashtable serializedlength:812233 lru:14320118 lru_seconds_idle:182

2.4 生产安全的全量普查脚本

–bigkeys 只给「最大的一个」,要全量捞出所有超标 key,用 SCAN 分批游标 + MEMORY USAGE,避免 KEYS 阻塞主线程:

import redis

r = redis.Redis(host="10.0.0.12", port=6379, decode_responses=True)
BIG_THRESHOLD = 10240  # 超过 10KB 记为大 key

cursor = 0
while True:
    cursor, keys = r.scan(cursor, count=500)
    for k in keys:
        size = r.memory_usage(k)      # O(1),对线上安全
        if size and size > BIG_THRESHOLD:
            print(f"BIGKEY {k} {size}B type={r.type(k)}")
    if cursor == 0:
        break

三、热 key:被忽视的单点瓶颈

3.1 热 key 的三种信号

热 key 在监控上往往表现为:个别节点的 CPU 和带宽明显高于其他分片;INFO commandstats 里某条命令(如 GET order:detail:1)调用量断层领先;或者客户端出现「集群很闲、单请求却很慢」的诡异延迟。它和 连接池耗尽、端口耗尽这类资源耗尽故障 不同,瓶颈不在连接,而在单 key 的访问热点。

3.2 客户端埋点 + 代理层统计

Redis 7.4+ 自带 --hotkeys 选项,由 LFU 计数器直接给出热点 key;老版本则靠代理层(Codis/Twemproxy/集群代理)访问日志聚合,或业务侧用滑动窗口统计:

# Redis 7.4+ 直接输出热 key(基于 LFU)
redis-cli -h 10.0.0.12 -p 6379 --hotkeys

# 代理层访问日志聚合(示例:统计每秒 TOP 热 key)
redis-cli info commandstats | grep -i "cmdstat_get"

四、根治方案对照表

诊断手段适用场景是否阻塞精度
redis-cli –bigkeys快速总量扫描被动采样,较安全低(仅每种最大)
MEMORY USAGE单 key 精准体积否(O(1))
SCAN + memory_usage 脚本全量普查大 key否(分批游标)
代理层 / –hotkeys热 key 识别

4.1 大 key 的拆分与异步删除

根治思路是「拆」和「缓」:把大 Hash 按字段哈希成多个子 key;大 List 改用分页或分片;大 String 压缩或移出 Redis。删除时绝不用 DEL,改用 UNLINK(异步惰性删除,Redis 4.0+),把阻塞从主线程挪到后台线程:

# 异步删除,不阻塞主线程
redis-cli UNLINK user:feed:8872

4.2 热 key 的打散与就近缓存

热 key 靠「分散」解决:对 key 做固定分片(如 16 副本轮询),把单点压力摊到多个分片;或在客户端加一层本地缓存(Caffeine/Guava),把极端热点挡在 Redis 之前。合理设计分布式锁时也要避开热 key,参考 Redis 分布式锁与高级数据结构实战 的锁粒度建议。

// 热 key 本地缓存 + 多副本打散(Java 侧)
LoadingCache<String, String> local = Caffeine.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(30, TimeUnit.SECONDS)
    .build(k -> redis.get(k));

// key 分片,把单点压力摊到 16 个分片
String shardKey = "order:detail:" + (hash(userId) % 16) + ":" + userId;

4.3 容量规划:给大 key 留足余量

根治之后还要防复发:给 Redis 实例预留至少 30% 内存余量,对写入频繁的大集合设置成员数上限与 TTL,必要时把冷数据下沉到持久化层(可结合 PostgreSQL 慢查询优化与索引调优 的思路做冷热分离)。容量规划与监控双管齐下,大 key 才不会再偷偷长回来。

五、真实案例:一次大促前的缓存排雷

某次大促压测,监控显示其中一个分片 CPU 长期 95%,其余分片不到 20%。按本文流程:先用 –bigkeys 捞出 user:feed 系列大 Hash(单 key 84MB),再用 –hotkeys 发现首页推荐位 feed:top 每秒 12 万次读。处理动作:把 84MB 的 Hash 按用户 ID 取模拆成 64 个子 key,并用 UNLINK 替换 DEL;feed:top 加 Caffeine 本地缓存(30 秒过期)+ 16 副本分片。整改后该分片 CPU 降到 35%,P99 延迟从 180ms 落到 9ms,大促当天零缓存故障。

六、把排雷做成日常:监控与复盘

排雷不是一次性任务。建议把 MEMORY USAGE 普查接入定时任务(低峰期跑),把热 key 统计接进监控大盘,对超过阈值的 key 自动告警。新上线前在预发环境跑一遍 –bigkeys,能从源头拦住大部分大 key。缓存稳了,上层建筑才稳——这也是我们在缓存三大问题防护之外,必须补上的「大 key / 热 key」这一课。

七、上线前排雷 checklist

把排雷沉淀成可执行的检查项,比事后救火省心得多。每次发版前过一遍这四条:

  • 大 key 预警:预发环境跑 –bigkeys,单 key 体积超 10KB 即预警,超 1MB 必须拆分。
  • 热 key 打散:监控大盘接入 LFU 热 key 统计,单 key QPS 超过分片均值 5 倍即做分片或本地缓存。
  • 删除规范:禁止 DEL 大 key,统一改用 UNLINK 异步删除,避免主线程抖动拖垮整实例。
  • 容量水位:实例预留 30% 内存,大集合设成员上限与 TTL,冷数据下沉持久层做冷热分离。
上一篇 2026 大模型 8-9 月盘点:开源爆发与 Agent 拐点
下一篇 ADR 架构决策记录实战:让技术选型可追溯可复盘