首先還是先看下緩存擊穿的一個基本概念:
如上圖,這個圖應該在其他博文中出現過很多次了,同樣,緩存擊穿就是在某個時刻。當某個熱點key失效的瞬間,大批量請求進來,造成數據庫壓力太大導致數據庫服務宕機。
當然關於緩存擊穿也是有對應的解決方法:
1,設置熱點key用不過期
2,使用分佈式鎖
備註:關於redis實現分佈式鎖,可以參考我的另一篇博客:https://blog.csdn.net/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);
}
}
可以看到現在是隻查詢一次數據庫了。