跳到主要内容

Redis 常见线上问题

问题

生产环境 Redis 突然变慢、大 Key 阻塞、热 Key 打爆单节点、缓存击穿,怎么处理?

答案

问题速查

问题表现紧急程度
大 Key操作/删除时阻塞
热 Key单节点 CPU 打满
缓存击穿DB 瞬间流量飙升
内存暴涨OOM / 淘汰频繁
集群倾斜某节点内存/QPS 过高

大 Key 问题

大 Key 定义:String > 10KB,Hash/List/Set > 5000 个元素。

发现大 Key
# 离线扫描(推荐)
redis-cli --bigkeys

# 指定阈值扫描
redis-cli --bigkeys --i 0.1 # 每 100ms 扫一个 key

# RDB 分析(不影响线上)
rdb --command memory dump.rdb --bytes 10240 --largest 100

大 Key 危害:

  • 操作延迟高(HGETALL 一个百万字段的 Hash 可能耗时秒级)
  • 删除时阻塞(DEL 一个大 Key 时 Redis 单线程卡住)
  • 集群迁移困难
大 Key 处理方案
// ❌ 直接删除大 Key(会阻塞)
redisTemplate.delete("big:key");

// ✅ 异步删除(Redis 4.0+,后台线程处理)
redisTemplate.unlink("big:key");

// ✅ 分批删除 Hash
String cursor = "0";
do {
ScanResult<Map.Entry<String, String>> result =
jedis.hscan("big:hash", cursor, new ScanParams().count(100));
for (Map.Entry<String, String> entry : result.getResult()) {
jedis.hdel("big:hash", entry.getKey());
}
cursor = result.getCursor();
} while (!"0".equals(cursor));

热 Key 问题

热 Key:某个 key 的 QPS 特别高,集中在一个 Redis 节点上。

热 Key 解决方案
// 方案 1:本地缓存(Caffeine)+ Redis 双层缓存
Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofSeconds(5)) // 短过期
.build();

public Object get(String key) {
// 先查本地缓存
Object value = localCache.getIfPresent(key);
if (value != null) return value;

// 本地没有再查 Redis
value = redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value);
}
return value;
}

// 方案 2:key 打散到多个节点
// 给 key 加后缀,分散到不同 slot
public Object getWithShard(String key) {
int shard = ThreadLocalRandom.current().nextInt(10);
String shardKey = key + ":" + shard;
Object value = redisTemplate.opsForValue().get(shardKey);
if (value == null) {
value = loadFromDB(key);
// 写入所有分片
for (int i = 0; i < 10; i++) {
redisTemplate.opsForValue().set(key + ":" + i, value, 10, TimeUnit.MINUTES);
}
}
return value;
}

缓存击穿实战

缓存击穿:热 Key 过期瞬间,大量请求穿透到 DB。

分布式锁 + 缓存重建
public Object getWithLock(String key) {
Object value = redisTemplate.opsForValue().get(key);
if (value != null) return value;

// 缓存未命中,加分布式锁防止穿透
String lockKey = "lock:" + key;
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);

if (locked) {
try {
// 双重检查
value = redisTemplate.opsForValue().get(key);
if (value != null) return value;

// 查 DB 并重建缓存
value = loadFromDB(key);
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
} finally {
redisTemplate.delete(lockKey);
}
} else {
// 没拿到锁,等待后重试
Thread.sleep(50);
return getWithLock(key);
}
return value;
}

集群数据倾斜

查看集群各节点内存
redis-cli --cluster info <host>:<port>

# 查看 slot 分布
redis-cli cluster slots
原因解决
大 Key 集中在某 slot拆分大 Key
Hash Tag 不当避免 {user} 让大量 key 落在同一 slot
热 Key本地缓存 + 分片

常见面试问题

Q1: Redis 突然变慢,排查思路?

答案

  1. slowlog get 10:查慢命令(KEYS、HGETALL 大 Key)
  2. info memory:内存是否打满,是否在频繁淘汰
  3. info clients:连接数是否过多
  4. info stats:看 keyspace_misses 缓存命中率
  5. 检查是否有大 Key 在做 DEL、过期
  6. 检查是否开启了 RDB/AOF 重写在 fork 子进程

详见 Redis 性能优化

Q2: 缓存穿透/击穿/雪崩的区别?

答案

缓存穿透缓存击穿缓存雪崩
定义查不存在的数据热 Key 过期大量 Key 同时过期
原因异常请求过期策略过期时间一致
方案布隆过滤器 / 空值缓存互斥锁 / 永不过期过期时间加随机值

详见 Redis 缓存问题

Q3: 大 Key 怎么预防?

答案

  • 设计阶段:控制集合类型元素数量,String 控制大小
  • 监控阶段:定时扫描大 Key + 告警
  • 使用阶段:避免 HGETALL / SMEMBERS,用 HSCAN / SSCAN 分批获取

相关链接