查询一个数据库和缓存中都不存在的key,每次请求都打到数据库,大量请求可能拖垃数据库。
缓存空对象
@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失效瞬间 | 互斥锁 / 逆机制 |
| 缓存雪崩 | 大量同时失效 | 随机过期 / 多级缓存 |
