大 Key / 热 Key 排查与拆分
一句话总结
大 Key 是”量”的问题——一个 key 塞了太多数据,读写都慢,拖死整个 Redis。热 Key 是”频”的问题——一个 key 被太多人同时抢着读,单节点 CPU 被打满。大 key 靠拆,热 key 靠散。
🌰 大 Key 篇:胖子是怎么拖死 Redis 的
你是 Redis,你是单线程
想象你是奶茶店唯一的店员(Redis 是单线程的)。
正常情况:
顾客 A:"我要一杯奶茶"
你:3 秒做好 → A 走了
顾客 B:"我要一杯柠檬茶"
你:3 秒做好 → B 走了
排队的人虽然多,但每个人 3 秒,不堵。
大 Key 来了:
顾客 C:"把我这 500 斤大米搬进店里"
你:???
你花了 5 分钟搬米(操作大 key)
这 5 分钟里,后面排了 100 个顾客
每个顾客都在等你搬完米才能点单
100 个人等 5 分钟 → 全跑了 ❌
这就是大 key 的问题:
Redis 是单线程,一次只处理一个请求。
你的请求耗时 5 分钟,
后面所有请求都得等着。
为什么会有大 Key?
常见的大 key 是怎么产生的:
① 日志全塞一个 List
业务代码:r.lpush("logs", new_log)
你觉得:就存个日志嘛,能有多大?
一年后:logs 这个 List 里面有 5 亿条数据
任何一次操作 logs → Redis 卡死 ❌
② 把 MySQL 一张表全塞进一个 String
业务代码:json.dumps(全量用户数据) → r.set("all_users", 大json)
一个 key 占了 2GB 内存
每次读它 → Redis 要返回 2GB 数据 → 网络打满 ❌
③ Hash 存了太多字段
一个 Hash 存了 1000 万个粉丝关系
查询 / 遍历 / 删除 → 全部巨慢 ❌
大 Key 的危害——一个具体的故事
你的系统里有一个 key 叫 "user:100:fans",
是个 Hash,存了 200 万个粉丝 ID。
这天你干了一件事:
HDEL user:100:fans some_fan
→ 删一个粉丝而已,应该很快吧?
但你在监控上看到:
Redis 的延迟从 1ms 飙到了 500ms
这个 HDEL 命令执行了 200ms
(因为 Hash 底层要维护大字典的 rehash)
这 200ms 里:
99 个其他请求被堵住了
其中有 10 个是用户下单的请求
用户点了下单按钮 → 转圈 200ms → 生气的走了 ❌
第二天又干了一件事:
你想看看这个 Hash 有多大
执行了 HLEN user:100:fans → 50ms
执行了 HGETALL user:100:fans → 直接 3 秒,超时了
怎么发现大 Key?
你在线上发现 Redis 突然变慢了。
怎么确定是不是大 key 搞的?——三招:
第一招:看慢查询日志
SLOWLOG GET 10
如果看到:
"HDEL user:100:fans" 耗时 200ms
"LRANGE logs 0 -1" 耗时 1500ms
"DEL big_set" 耗时 3000ms
→ 八成是遇上大 key 了
第二招:redis-cli --bigkeys(最常用)
终端敲一行命令:
redis-cli --bigkeys
它会告诉你:
Biggest String found 'user:100:profile' → 2.5 MB
Biggest Hash found 'user:100:fans' → 200万字段
Biggest List found 'logs' → 5000万条
第三招:MEMORY USAGE 精确查
MEMORY USAGE user:100:fans
→ 52428800(50MB)
一个大 key 占了 50MB,别的 key 才几百字节
大 Key 怎么办?——拆!拆!拆!
核心思路就一个字:拆。
怎么拆看具体场景:
场景 1:日志 List 太大了(5000 万条)
不改代码不行。
原来:一个 logs 里塞了所有日志
改成:logs:2026-07-01, logs:2026-07-02, ...
每天一个 key,7 天前的自动淘汰。
效果:最大 key 从 5000 万条变成 100 万条。
场景 2:粉丝 Hash 太大了(200 万粉丝)
把一个大 Hash 拆成 200 个小 Hash。
粉丝 ID 对 200 取模,决定去哪个小 Hash。
原来是:
user:100:fans → {fid1: name1, ..., fid200万: name200万}
改成:
user:100:fans:0 → {fid1: name1, ..., fid1万: name1万}
user:100:fans:1 → {fid1万+1: ...}
...
user:100:fans:199 → {...}
查某个粉丝时先算槽位:
slot = fan_id % 200
HGET user:100:fans:{slot} fan_id
每个小 Hash 只有 1 万条,快得很。
场景 3:大 String 太大了(存了个 10MB 的 JSON)
两个方向:
① 拆字段——不要把整个对象序列化成一个 String
改成用 Hash,一个字段存一个属性
你不需要所有字段时,只读需要的字段就快多了
② 压缩——非要用 String,就先压缩再存
10MB 的 JSON → GZIP 压缩 → 剩 1~2MB
读的时候解压回来
场景 4:冷热分离——不是所有数据都要在 Redis 里
3 年前的订单也放 Redis?
一个月前就不查的数据也放 Redis?
做法:Redis 只存最近 7 天的热数据
超过 7 天的自动淘汰或搬到磁盘
用户查历史数据 → 走 MySQL
不占 Redis 内存,也没有大 key 的问题
🌰 热 Key 篇:顶流是怎么打爆 Redis 的
一个真实的故事
双十一零点,某电商网站。
商品 "iPhone18" 的库存 key:
"stock:iphone18" → 值就是库存数字
平时:
一秒钟几百个人点进来看看,Redis 轻松处理。
双十一零点一秒:
10 万人同时点抢购
全部去读 "stock:iphone18" 这个 key
出事了:
这个 key 所在的 Redis 节点 CPU 瞬间 100%
其他 key(用户登录、购物车、订单…)也在这个节点上
但它们全堵住了,因为 CPU 在忙着处理 "stock:iphone18"
你看到监控:
这个节点的延迟:1ms → 5000ms
其他节点:正常
→ "集群倾斜"——一个人忙死,其他人闲死
这就是热 key 的问题:
不是这个 key 有多大(就是一个数字),
是太多人同时抢着读它。
Redis 单线程处理不过来。
热 Key 怎么发现?
三招,从简单到复杂:
第一招:redis-cli --hotkeys
终端敲一下 → 看哪些 key 访问频率最高
前提是你配置了 LFU 淘汰策略
第二招:客户端埋点统计
在你的代码里,每次操作 Redis 之前,
往本地计数器里 +1。
每分钟打一次日志,看哪些 key 访问最多。
土办法,但很实用。
第三招:看 Redis 的 CPU 和延迟监控
如果某个 Redis 节点的 CPU 是其他节点的 5 倍
→ 这个节点上大概率有热 key
热 Key 怎么办?——就一个字:散
核心思路:把流量分散到多个 Redis 节点上。
方案 1:本地缓存(最有效)
你的应用服务器自己存一份热 key 的数据。
比如 stock:iphone18 = 99,你缓存到本地内存里,
过期时间设 1 秒。
效果:
10 万请求 → 9.9 万直接从本地内存拿
只有 1000 个请求穿透到 Redis
相当于每个应用服务器自己挡掉 99% 的流量。
Redis 的压力直接降到 1% ✅
方案 2:把 key 散成多个副本
热 key 之所以热,是因为所有请求都打在同一个槽上。
那我们把 key 复制几份,分散到不同槽:
原来:
所有客户端读 "stock:iphone18" → 同一个节点
改成:
客户端写入时写到 10 个 key:
"stock:iphone18:0"、"stock:iphone18:1"、...
客户端读的时候随机读一个:
index = random(0, 9)
GET "stock:iphone18:{index}"
流量从 1 个节点分散到 10 个节点。
代价:数据有短暂不一致(一个 key 更新了,另一个还没更新)
适用:秒杀场景下可以接受 1 秒内的不一致
方案 3:读写分离 + 多从库读
主库写,三个从库读。
热 key 的读请求分散到三个从库。
每个从库只扛 1/3 的流量。
方案 4:限流——最后防线
上面方案都用了,流量还是大到打穿 Redis。
那就只能限流了——每秒最多处理 10 万次请求,
超出的直接返回"稍后重试"。
宁可拒绝一部分用户,也不能让 Redis 被打挂。
因为 Redis 一挂,所有用户都用不了了。
一句话讲清
延伸提问:"你线上遇到过 Redis 大 key 或者热 key 吗?"
你:
"遇到过。之前项目里有个日志 List 堆积了 5000 万条,
一次 LRANGE 操作把 Redis 卡了好几秒。
排查是先看慢查询日志,发现有几个 DEL 和 LRANGE 特别慢,
然后执行 redis-cli --bigkeys 确认,
发现这个 logs key 是最大的。
解决是按天拆分,改成 logs:yyyy-mm-dd 的格式,
加上过期时间自动淘汰。之后监控就没再出现过。
热 key 的话,
我们之前做活动的时候有个商品被大量用户抢购,
那个库存 key 一秒钟被读十几万次,
导致它所在的 Redis 节点 CPU 打满。
也是从监控发现其他节点正常,就这个节点异常。
解决是加了一层本地缓存,
把数据在应用内存里缓存 1 秒,
90% 的请求被本地挡住了,
Redis 的压力基本解除了。"
记忆口诀
大 key 是”量”——一个 key 塞太多,单线程下操作它等于堵住所有人。拆成小份就好了:按时间拆、按哈希拆、冷热分离。
热 key 是”频”——太多人同时读同一个 key。把流量散开就好了:本地缓存扛 99%、散列成多个副本、多从库分摊读。
发现大 key → —bigkeys + 慢查询。发现热 key → —hotkeys + 客户端统计。
核心思想:大 key 要拆,热 key 要散。
速记卡(面试闪卡)
Q1:一句话讲清「大 Key / 热 Key 排查与拆分」到底是什么?
A:大 Key 是一个 key 塞太多数据拖慢单线程 Redis,热 Key 是一个 key 被太多人同时读打满 CPU;大 Key 拆、热 Key 散。
Q2:大 Key 怎么拖死 Redis? —— 怎么理解?
A:像奶茶店唯一店员被叫去搬 500 斤大米——Redis 单线程一次只处理一个请求,操作大 key(如 5000 万条日志 List)要几分钟,后面 100 个顾客全干等跑光。大 Key 是”量”的问题。
Q3:怎么发现大 Key? —— 怎么理解?
A:像查哪个包裹超重——三招:① SLOWLOG GET 看慢命令(HDEL/LRANGE/DEL 耗时异常);② redis-cli —bigkeys 扫出最大 String/Hash/List;③ MEMORY USAGE key 精确看占用字节。
Q4:热 Key 又是怎么回事? —— 怎么理解?
A:像双十一抢 iPhone 库存——10 万人同一秒读 stock:iphone18,它所在节点 CPU 瞬间 100%,其他 key 全堵(集群倾斜)。热 Key 是”频”的问题,不是大,是太多人同时抢读同一个。
Q5:大 Key 和热 Key 怎么治? —— 怎么理解?
A:大 Key 像把大货拆小件——按天拆日志(logs:yyyy-mm-dd)、Hash 按 id 取模拆 200 份、大 String 拆字段或压缩、冷热分离只留 7 天热数据。热 Key 像把人流散开——本地缓存扛 99%、复制成多副本随机读、多从库分摊、限流兜底。
Q6:核心速记主线有哪些?
-
大Key是”量”:一个key塞太多,单线程下操作它等于堵住所有人
-
大Key发现:SLOWLOG慢日志 + redis-cli —bigkeys + MEMORY USAGE
-
热Key是”频”:太多人同时读同一key,节点CPU打满(集群倾斜)
-
大Key要拆(按时间/哈希/冷热分离),热Key要散(本地缓存/多副本/多从库/限流)
口诀
A:大Key是胖子,一人搬米堵全店。
热Key是顶流,万人抢读CPU爆。
大Key靠拆小份,按天按模冷热分。
热Key靠散开,本地缓存扛九成。