Redis突然变慢,排查发现是BigKey惹的祸

线上Redis响应时间从平均1ms飙到了50ms,业务接口全都变慢了。

查了半天,最后发现是一个BigKey导致的。记录一下排查过程。


问题现象

监控数据

  • Redis平均响应时间:1ms → 50ms
  • 业务接口P99延迟:50ms → 500ms
  • Redis CPU:20% → 80%
  • 内存使用:正常

特点

  • 突然变慢,不是逐渐变慢
  • 所有命令都变慢,不只是特定命令
  • 重启后好一段时间,然后又变慢

排查过程

Step 1:查看慢查询日志

bash 复制代码
redis-cli

# 查看慢查询日志
SLOWLOG GET 20

输出:

复制代码
1) 1) (integer) 1001
   2) (integer) 1702345678
   3) (integer) 45123      # 微秒,约45ms
   4) 1) "HGETALL"
      2) "user:session:12345"

发现大量HGETALL命令耗时几十毫秒,正常应该是亚毫秒级。

Step 2:查看这个Key的信息

bash 复制代码
# 查看Key类型
TYPE user:session:12345
# hash

# 查看Hash的字段数量
HLEN user:session:12345
# 182356

# 查看Key占用内存
DEBUG OBJECT user:session:12345
# serializedlength:15728640  约15MB

问题找到了! 这个Hash有18万个字段,占用15MB内存。

这就是BigKey,对它执行HGETALL要把18万个字段全部遍历,当然慢。

Step 3:查找其他BigKey

bash 复制代码
# Redis 4.0+ 可以用 --bigkeys 扫描
redis-cli --bigkeys

# 或者用 SCAN 配合 DEBUG OBJECT
redis-cli --scan --pattern '*' | while read key; do
  size=$(redis-cli DEBUG OBJECT "$key" 2>/dev/null | grep -oP 'serializedlength:\K\d+')
  if [ "$size" -gt 1048576 ]; then  # 大于1MB
    echo "$key: $size bytes"
  fi
done

扫描结果发现了多个BigKey:

复制代码
user:session:12345: 15728640 bytes (15MB)
cache:product:list: 8388608 bytes (8MB)
temp:import:batch: 5242880 bytes (5MB)

Step 4:分析业务逻辑

查代码发现问题:

java 复制代码
// 问题代码:把整个session存成一个大Hash
@Override
public void saveSession(String sessionId, Map<String, Object> data) {
    String key = "user:session:" + sessionId;
    // 每次访问都往里加数据,从来不清理
    redisTemplate.opsForHash().putAll(key, data);
}

// 获取时用HGETALL
public Map<String, Object> getSession(String sessionId) {
    String key = "user:session:" + sessionId;
    return redisTemplate.opsForHash().entries(key);  // HGETALL
}

问题

  1. Session数据一直往Hash里加,不删除
  2. 时间一长,Hash就变成了BigKey
  3. 每次获取Session都用HGETALL,遍历整个Hash

BigKey的危害

1. 阻塞单线程

Redis是单线程的,操作BigKey时会阻塞其他命令:

复制代码
正常Key(1KB):  1ms完成
BigKey(10MB): 50ms完成

这50ms内其他所有命令都在排队等待

2. 网络带宽压力

复制代码
每次HGETALL返回15MB数据
1秒请求10次 = 150MB/s
网络可能成为瓶颈

3. 内存不均衡

如果是Redis集群,BigKey会导致某个节点内存远大于其他节点。

4. 删除时阻塞

bash 复制代码
DEL user:session:12345  # 删除15MB的Key,可能阻塞好几秒

解决方案

方案一:拆分BigKey

把大Hash拆成多个小Hash:

java 复制代码
// 优化前:一个大Hash
user:session:12345 → {field1: v1, field2: v2, ... field180000: v180000}

// 优化后:按照某种规则拆分
user:session:12345:0 → {field1: v1, ... field1000: v1000}
user:session:12345:1 → {field1001: v1001, ... field2000: v2000}
...

方案二:改用合适的数据结构

Session数据不需要存18万个字段,只需要保留最近访问的数据:

java 复制代码
// 使用String存储序列化后的数据,设置过期时间
public void saveSession(String sessionId, SessionData data) {
    String key = "user:session:" + sessionId;
    String json = JSON.toJSONString(data);
    redisTemplate.opsForValue().set(key, json, 30, TimeUnit.MINUTES);
}

方案三:避免HGETALL

java 复制代码
// 优化前:获取整个Hash
Map<String, Object> all = redisTemplate.opsForHash().entries(key);

// 优化后:只获取需要的字段
Object value = redisTemplate.opsForHash().get(key, "targetField");

// 或者批量获取部分字段
List<Object> values = redisTemplate.opsForHash().multiGet(key, Arrays.asList("f1", "f2"));

方案四:异步删除BigKey

bash 复制代码
# Redis 4.0+ 支持异步删除
UNLINK user:session:12345  # 异步删除,不阻塞

# 或者渐进式删除Hash
# 每次删1000个字段
HSCAN user:session:12345 0 COUNT 1000
HDEL user:session:12345 field1 field2 ... field1000

最终解决

  1. 临时处理 :用UNLINK异步删除那几个BigKey
  2. 代码修复:Session改用String存储,设置30分钟过期
  3. 添加监控:定期扫描BigKey,超过1MB告警

BigKey标准

数据类型 BigKey阈值 说明
String > 10KB 单个值太大
Hash > 5000字段 或 > 10MB 字段太多或总大小太大
List > 5000元素 元素太多
Set > 5000成员 成员太多
ZSet > 5000成员 成员太多

排查命令汇总

bash 复制代码
# 查看慢查询
SLOWLOG GET 20

# 扫描BigKey
redis-cli --bigkeys

# 查看Key类型
TYPE <key>

# 查看Hash字段数
HLEN <key>

# 查看List长度
LLEN <key>

# 查看Set成员数
SCARD <key>

# 查看内存占用(需要开启)
MEMORY USAGE <key>

# 查看Key详情
DEBUG OBJECT <key>

# 渐进式扫描
HSCAN <key> 0 COUNT 100

# 异步删除
UNLINK <key>

预防措施

1. 设计阶段

复制代码
✅ 预估数据量,避免无限增长
✅ 设置合理的过期时间
✅ 考虑数据拆分策略

2. 开发阶段

复制代码
✅ 避免使用HGETALL、SMEMBERS等全量命令
✅ 大数据量使用SCAN系列命令
✅ 删除大Key使用UNLINK

3. 运维阶段

复制代码
✅ 定期扫描BigKey
✅ 监控慢查询
✅ 设置maxmemory-policy

经验总结

现象 可能原因
所有命令都变慢 BigKey阻塞
特定命令变慢 该命令操作了BigKey
内存突然增长 写入了BigKey
主从同步延迟 BigKey传输

这次的坑:Session数据只写不删,时间一长变成了18万字段的BigKey。

教训

  1. Redis的Key一定要设置过期时间
  2. 避免使用HGETALL等全量命令
  3. 定期扫描BigKey,加入监控

有问题评论区交流~

复制代码
相关推荐
z123456789869 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
程序猿DD9 小时前
一个 API Key,统一调用大模型、生图和联网搜索
数据库·网关
TlSfoward10 小时前
抓包代理链路下的 TLS 指纹变化分析 TLSFOWARD抓包工具
数据库·爬虫·网络协议·搜索引擎·php
数智化管理手记10 小时前
账龄分析手工统计易遗漏?自动账龄分析工具怎么搭建
大数据·网络·数据库·人工智能·数据挖掘
LabVIEW开发10 小时前
NI Package Manager安装LabVIEW时InternalServerError错误
网络·数据库·labview·labview知识·labview功能·labview程序
SelectDB10 小时前
PostgreSQL 实时同步到 Apache Doris:一站式 CDC 解决方案
数据库
Database_Cool_11 小时前
AI Agent 长期记忆怎么存?阿里云 PolarDB-X 对接 Mem0 记忆框架实践
数据库·阿里云
时空无限11 小时前
vllm 缓存对模型启动时间的影响
缓存·vllm
会编程的土豆11 小时前
数据库范式
数据库·oracle
L16247611 小时前
MySQL 全环境生产快速安装 + 完整配置手册(整合国产银河麒麟适配 + 主从补充版)
数据库·mysql