Redis缓存实战:彻底解决缓存穿透、击穿、雪崩三大难题

一、缓存穿透

问题描述

查询一个数据库和缓存中都不存在的key,每次请求都打到数据库,大量请求可能拖垃数据库。

解决方案

1. 缓存空对象

@Service public class UserService { @Autowired private RedisTemplate<String, Object> redisTemplate; @Autowired private UserMapper userMapper; public User getUserById(Long id) { String key = "user:" + id; // 查询缓存 User user = (User) redisTemplate.opsForValue().get(key); if (user != null) { return user.getId() == null ? null : user; // 返回空对象表示不存在 } // 查询数据库 user = userMapper.selectById(id); if (user == null) { // 缓存空对象,设置短过期时间 redisTemplate.opsForValue().set(key, new User(), 5, TimeUnit.MINUTES); return null; } redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES); return user; } }

2. 布隆过滤器:将所有合法key存入Bloom Filter,请求先过滤器,不存在的key直接拖到。

回到顶部

二、缓存击穿

问题描述

查询一个缓存刚好失效的key,大量请求同时打到数据库。

解决方案

互斥锁(推荐)

public User getUserById(Long id) { String key = "user:" + id; User user = (User) redisTemplate.opsForValue().get(key); if (user != null) return user; // 加分布式锁 String lockKey = "lock:user:" + id; Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { try { // 再次查询缓存(可能其他线程已经写入) user = (User) redisTemplate.opsForValue().get(key); if (user == null) { user = userMapper.selectById(id); redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES); } } finally { redisTemplate.delete(lockKey); } } else { // 未获得锁,稍等重试 Thread.sleep(50); return getUserById(id); } return user; }

回到顶部

三、缓存雪崩

问题描述

大量缓存key同时失效,大量请求同时打到数据库。

解决方案

1. 过期时间加随机偶数

// 设置缓存时加随机偶数,避免同时失效 int randomExpire = 30 + new Random().nextInt(10); // 30~40分钟 redisTemplate.opsForValue().set(key, user, randomExpire, TimeUnit.MINUTES);

2. 多级缓存:本地缓存(Caffeine)+ Redis两级,即使 Redis雪崩也有本地缓存托底。

3. 缓存预热:项目启动时提前将热点数据加载到缓存。

回到顶部

总结

问题原因解决方案
缓存穿透key不存在空对象 / 布隆过滤器
缓存击穿key失效瞬间互斥锁 / 逆机制
缓存雪崩大量同时失效随机过期 / 多级缓存