深夜被叫醒两次:一次Redis热点Key打满带宽,一次大Value拖垮主从,我把这两个问题彻底讲清楚
兄弟们,你们有没有试过一个晚上被叫醒两次的经历?第一次是凌晨1点,Redis CPU突然飙到95%,所有接口超时。第二次是凌晨3点,主从同步延迟从几毫秒飙升到几十秒,从库一直追不上。
第一次查出来是热点Key,第二次查出来是大Value。这两个“杀手”虽然都叫“Redis性能问题”,但症状完全不同,排查思路也天差地别。今天就把这两次翻车经历完整复盘出来。
一、第一次事故:热点Key打爆CPU
1.1 现场还原
凌晨1点,运维群炸了:“Redis CPU 95%!服务全部超时!”
登录服务器后,redis-cli --stat 的输出触目惊心:
------- data ------ --------------------- load -------------------- - child - keys mem clients blocked requests connections 100234 8.2G 1500 0 4823941 (+0) 1501 100235 8.2G 1500 0 5234982 (+411041) 1501 100235 8.2G 1500 0 5782391 (+547409) 1501
QPS从平时的1万瞬间飙到5万多,单次命令的响应时间从平均0.2ms飙升到50ms。
1.2 根因分析
用 redis-cli --bigkeys 分析发现,所有流量都打在了同一个Key上:
> redis-cli --bigkeys ... Biggest string found 'hot:product:10086' has 256 bytes
这个Key存储的是某个爆款商品的信息,由于营销活动,所有用户都在疯狂访问它。QPS集中在一个Key上,导致负责该Key的Redis节点CPU被瞬间打满。

1.3 热点Key的“四宗罪”
| 故障表现 | 原因 | 影响程度 |
|---|---|---|
| CPU瞬时飙高 | 单Key QPS过高,Redis主线程被占满 | ⚠️⚠️⚠️ |
| 网络带宽打满 | 热点Value频繁传输,占用带宽 | ⚠️⚠️ |
| 缓存穿透/雪崩 | 热点Key过期瞬间,大量请求直击DB | ⚠️⚠️⚠️ |
| 集群热点倾斜 | Cluster模式下,单一节点负载远高于其他节点 | ⚠️⚠️⚠️ |
1.4 排查命令
# 查看当前哪些Key被频繁访问(需要开启monitor,生产慎用) redis-cli monitor | head -100 # 统计各Key的访问频率(使用redis-cli --hotkeys) redis-cli --hotkeys # 查看当前QPS redis-cli INFO stats | grep instantaneous_ops_per_sec # 查看CPU使用率 redis-cli INFO cpu
1.5 热点Key解决方案

方案一:本地缓存(Caffeine)+ 一致性Hash
@Component
public class HotKeyCache {
private final Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(10000) // 最多缓存1万条
.expireAfterWrite(1, TimeUnit.MINUTES) // 1分钟过期
.build();
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Object getData(String key) {
// L1: 本地缓存
Object data = localCache.getIfPresent(key);
if (data != null) {
return data;
}
// L2: Redis
data = redisTemplate.opsForValue().get(key);
if (data != null) {
localCache.put(key, data);
return data;
}
// L3: DB
data = queryFromDB(key);
if (data != null) {
redisTemplate.opsForValue().set(key, data, 1, TimeUnit.HOURS);
localCache.put(key, data);
}
return data;
}
}
方案二:Key拆分 + 随机后缀
热点Key可以通过增加随机后缀分散流量:
public String getHotProduct(Long productId) {
// 将热点Key拆分成N个副本,随机访问
int N = 10;
int slot = ThreadLocalRandom.current().nextInt(N);
String key = "hot:product:" + productId + ":" + slot;
return redisTemplate.opsForValue().get(key);
}
每次写入时,同时更新所有副本:
public void setHotProduct(Long productId, Object data) {
for (int i = 0; i < 10; i++) {
String key = "hot:product:" + productId + ":" + i;
redisTemplate.opsForValue().set(key, data, 1, TimeUnit.HOURS);
}
}
二、第二次事故:大Value拖垮主从
2.1 现场还原
凌晨3点,报警再次响起:“主从延迟超过60秒,从库数据严重落后!”
> redis-cli INFO replication # Replication role:master connected_slaves:1 slave0:ip=192.168.1.2,port=6379,state=online,offset=4823941,lag=60
lag已经60秒了。主库写入正常,但从库迟迟追不上。
2.2 根因分析
检查后发现某个Key的Value达到了8MB——这是一个用户画像的JSON,包含了该用户的所有行为轨迹。
# 查看大Key redis-cli --bigkeys # 最大Key:user:profile:12345,Value大小:8.2MB
每次更新这个Key时,8MB的数据需要:
-
主库写入AOF(8MB)
-
传输到从库(8MB网络流量)
-
从库加载(8MB内存操作)
-
写入RDB(8MB)
一次性传输8MB,在网络条件一般的情况下,几十毫秒就过去了。频繁更新时,从库一直被大Value“卡住”,无法追平。
2.3 大Value的“五宗罪”
| 故障表现 | 原因 | 影响程度 |
|---|---|---|
| 内存碎片化严重 | 大Value频繁分配/释放,jemalloc无法有效回收 | ⚠️⚠️ |
| 慢查询/阻塞 | Redis单线程处理大Value,读写耗时过长 | ⚠️⚠️⚠️ |
| AOF/RDB放大 | 大Value导致AOF重写和RDB生成耗时剧增 | ⚠️⚠️ |
| 主从复制延迟 | 大Value传输占用带宽,从库回放慢 | ⚠️⚠️⚠️ |
| 网络抖动/超时 | 大Value传输时间长,易超时重传 | ⚠️⚠️ |

2.4 大Value排查命令
# 查找大Key redis-cli --bigkeys # 查看单个Key的内存占用(Redis 4.0+) MEMORY USAGE user:profile:12345 # 查看内存碎片率 redis-cli INFO memory | grep mem_fragmentation_ratio
2.5 大Value解决方案
方案一:大Value拆分
// 将一个大JSON拆分成多个小Key
public void saveUserProfile(Long userId, UserProfile profile) {
// 原方案:一个Key存所有
// redis.set("user:profile:" + userId, JSON.toJSONString(profile));
// 新方案:按模块拆分
redis.hset("user:profile:" + userId, "base", JSON.toJSONString(profile.getBase()));
redis.hset("user:profile:" + userId, "behavior", JSON.toJSONString(profile.getBehavior()));
redis.hset("user:profile:" + userId, "preference", JSON.toJSONString(profile.getPreference()));
}
方案二:压缩存储
public void saveCompressed(String key, Object data) {
byte[] json = JSON.toJSONBytes(data);
// 使用GZIP压缩
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(baos)) {
gzip.write(json);
}
redisTemplate.opsForValue().set(key, baos.toByteArray());
}
压缩率通常在70%-90%,8MB的数据压缩后可能只有1-2MB。
方案三:冷热分离
| 数据类型 | 存储方式 | 大小 |
|---|---|---|
| 高频访问字段 | Redis Hash(热) | < 1KB |
| 中频访问字段 | Redis Hash(温) | 1-10KB |
| 低频访问字段 | MySQL/对象存储(冷) | > 10KB |
将用户画像按访问频率分层存储,减少Redis中大Value的存储。
三、热点Key vs 大Value:快速诊断对比
| 故障现象 | 热点Key | 大Value |
|---|---|---|
| CPU飙升 | ✅ 高QPS导致 | ❌ 低频大Value不会,但高频会 |
| 带宽打满 | ✅ 高频小包 | ✅ 低频大包 |
| 主从延迟 | ❌ 小Value影响小 | ✅ 大Value传输慢 |
| 慢查询 | ❌ 通常不慢 | ✅ 读写耗时 |
| 内存碎片 | ❌ 影响小 | ✅ 频繁分配释放 |
| 集群倾斜 | ✅ 流量倾斜 | ✅ 存储倾斜 |

四、生产环境避坑清单
4.1 预防措施
| 措施 | 说明 |
|---|---|
| 合理设计Key | 避免单个Key过大,建议单个Value < 10KB |
| 设置合理TTL | 热点Key设置永不过期+后台刷新,避免缓存击穿 |
| 开启大Key监控 | 使用redis-cli --bigkeys定期巡检 |
| 配置告警 | CPU > 80%、主从延迟 > 10s 立即告警 |
| 客户端配置 | 设置连接超时、读取超时,避免大Value拖垮连接 |
4.2 监控指标
# 日常巡检命令集 redis-cli INFO stats | grep instantaneous_ops_per_sec # QPS redis-cli INFO memory | grep mem_fragmentation_ratio # 碎片率 redis-cli INFO replication | grep lag # 主从延迟 redis-cli --bigkeys -i 0.1 # 扫描大Key redis-cli --hotkeys # 扫描热点Key(4.0+)
4.3 应急处理
| 故障 | 应急措施 | 根治方案 |
|---|---|---|
| 热点Key打爆CPU | 临时增加从节点分担读 | 本地缓存 + Key拆分 |
| 大Value导致主从延迟 | 暂停该Key的更新,先让从库追上 | Value拆分 + 压缩 |
五、避坑总结
-
热点Key和大Value是两类不同的问题——诊断方向完全不同,看CPU还是看流量是关键区分点
-
热点Key靠“散”:本地缓存、Key拆分、读写分离,把流量分散出去
-
大Value靠“拆”:拆分存储、压缩存储、冷热分离,把数据打散
-
定期巡检是底线:
--bigkeys和--hotkeys要纳入日常巡检 -
监控告警是生命线:没有告警的故障只会越来越严重
兄弟们,你们在生产环境中遇到过热点Key还是大Value的问题?是怎么发现和解决的?评论区聊聊!
浙公网安备 33010602011771号