首先还是先看下缓存击穿的一个基本概念:
如上图,这个图应该在其他博文中出现过很多次了,同样,缓存击穿就是在某个时刻。当某个热点key失效的瞬间,大批量请求进来,造成数据库压力太大导致数据库服务宕机。
当然关于缓存击穿也是有对应的解决方法:
1,设置热点key用不过期
2,使用分布式锁
备注:关于redis实现分布式锁,可以参考我的另一篇博客:https://blog.youkuaiyun.com/baomw/article/details/84931234
当然当有了分布式锁之后,我们该如何实现呢?可以看下如下案例:
public User queryById(String id) {
Jedis jedis = jedisPool.getResource();
String userStr = jedis.get(id);
//先查缓存,查到直接返回
if (StringUtils.isNotEmpty(userStr)) {
return JSONObject.parseObject(userStr, User.class);
}
//模拟并发效果这边在查缓存和数据库之间延时50ms
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
//查不到差数据库
System.out.println("==========开始查数据库============");
User user = dao.queryById(id);
jedis.setex(user.getId(), 10, JSONObject.toJSONString(user));
return user;
}
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
final BaomwTestService cityService = ac.getBean(BaomwTestService.class);
//模拟并发效果这边启动10个线程同时做数据库查询操作
for (int i=0;i<10;i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(cityService.queryById("2"));
}
}).start();
}
}
可以看到,十次都查询了数据库,如果是高并发场景下,成千上万个线程同时进来,那么数据库压力显然是受不住的。
当然解决方案就是加分布式锁,这边可以看下案例
public User queryById(String id) {
Jedis jedis = jedisPool.getResource();
//先查缓存,查到直接返回
String userStr = jedis.get(id);
if (StringUtils.isNotEmpty(userStr)) {
return JSONObject.parseObject(userStr, User.class);
}
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
//对查询健加锁,使锁的粒度最小
lock.lock("locak"+id);
//这里需要再次查询缓存,因为可能会出现第一波并发线程同时到达:lock.lock("locak"+id);的
//位置,虽然是每次只有一个线程会拿到锁并执行,但是已经到这个位置的线程还是都会去查询一遍数据库
//所以这边必须加一个查询缓存的操作
userStr = jedis.get(id);
if (StringUtils.isNotEmpty(userStr)) {
return JSONObject.parseObject(userStr, User.class);
}
try {
//查不到差数据库
System.out.println("==========开始查数据库============");
User user = dao.queryById(id);
jedis.setex(user.getId(), 100, JSONObject.toJSONString(user));
return user;
}finally {
//为了预防死锁,这边再finally加解锁操作
lock.unlock("locak"+id);
}
}
可以看到现在是只查询一次数据库了。