现象
某个热点数据设置了过期时间。
过期瞬间并发请求同时打到数据库,数据库压力突然升高。
简单模拟
伪代码如下:
public String getData(String key) {
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
value = queryFromDb(key);
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.SECONDS);
}
return value;
}
当 key 过期时,多个线程同时进入 if 判断。
并发测试
简单用多线程模拟:
ExecutorService pool = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
pool.execute(() -> {
getData("hot_key");
});
}
在 key 失效瞬间,可以看到数据库查询被执行多次。
日志打印:
处理方式
使用互斥锁控制:
public String getData(String key) {
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
synchronized (this) {
value = redisTemplate.opsForValue().get(key);
if (value == null) {
value = queryFromDb(key);
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.SECONDS);
}
}
}
return value;
}
或者使用 Redis 的 setnx 做分布式锁。
补充
缓存击穿一般出现在:
- 热点数据
- 过期时间一致
- 高并发场景
和缓存穿透不同,击穿是 key 存在,只是刚好过期。