讀多寫少用緩存,寫多讀少用隊列。
查詢緩存,查不到取數據庫,放入緩存。
public String query(){
// 從緩存中獲取數據
String key = "key";
String value = redisService.get(key);//高併發場景下,所有線程進來查詢緩存,緩存中沒有數據時,來不及將數據庫中數據放入緩存,全部去查詢數據庫
if(value != null){
System.out.println("從緩存中獲取數據!");
return value;
}
// 從數據庫中獲取數據
value = dataService.get(key);
System.out.println("從數據庫中獲取數據!");
// 將數據插入緩存
redisService.set(key, value, 5*60);
return value;
}
一、緩存失效
1、高峯期大面積緩存key失效 / 緩存掛掉
解決:設置不同key不同失效時間,根據業務場景設置,或採用隨機數設置。避免相同失效時間,導致一時大量數據失效,全部查詢數據庫。
2、局部高峯期,熱點緩存key失效
導致熱點數據獲取海量請求直擊數據庫。
二、緩存雪崩
因緩存服務掛掉或者熱點緩存失效,導致大量請求查詢數據庫,數據庫服務器壓力過大,數據庫連接不夠用或者數據庫處理不過來,導致整個系統不可用。依賴數據庫的其他系統面臨崩潰風險。
解決:
增加互斥鎖,拿到鎖的線程查詢數據庫,寫入緩存,其他線程判斷緩存是否存在,此時之前拿到鎖的線程已經把數據寫入了,緩存存在,不在查詢數據庫。
synchronized:同步關鍵字,導致線程一直等待鎖,全部請求排隊阻塞影響效率。
public String query(){
// 從緩存中獲取數據
String key = "key";
String value = redisService.get(key);
if(value != null){
System.out.println("從緩存中獲取數據!");
return value;
}
synchronized (this) {
// 從數據庫中獲取數據
value = dataService.get(key);
System.out.println("從數據庫中獲取數據!");
// 將數據插入緩存
redisService.set(key, value, 5*60);
return value;
}
}
lock:靈活獲取鎖,釋放鎖。
lock(); //獲取鎖,多個線程爭搶鎖,搶不到一直等待,阻塞,不可中斷。
lockInterruptibly(); //獲取鎖,等待,阻塞,可中斷。
tryLock(); //嘗試獲取鎖,取不到返回false
tryLock(long l, TimeUnit timeunit); //指定時間內嘗試獲取鎖,取不到返回false
unlock(); //釋放鎖
Lock lock = new ReentrantLock();
public String query(){
// 1.從緩存中獲取數據
String key = "key";
String value = redisService.get(key);
if(value != null){
System.out.println("從緩存中獲取數據!");
return value;
}
lock.lock();// 所有線程排隊獲取鎖,只有1個能拿到
try {
value = redisService.get(key);// 二次校驗
if(value != null){
System.out.println("從緩存中獲取數據!");
return value;
}
// 2.從數據庫中獲取數據
value = dataService.get(key);
System.out.println("從數據庫中獲取數據!");
// 3.將數據插入緩存
redisService.set(key, value, 5*60);
return value;
} finally{
lock.unlock();
}
}
優點:簡單有效,請求不會全部湧向數據庫;適用範圍廣
缺點:鎖會阻塞所有線程,排隊獲取鎖,加大等待時長,用戶體驗差; 鎖的顆粒度粗,不同的key被同一把鎖阻塞(針對不同key單獨加鎖)
針對不同key單獨加鎖:
@Autowired
RedisService redisService;
@Autowired
DataService dataService;
ConcurrentHashMap<String, String> mapLock = new ConcurrentHashMap<String, String>();//map記錄鎖狀態
public String query(){
// 1.從緩存中獲取數據
String key = "key";
String value = redisService.get(key);
if(value != null){
System.out.println("從緩存中獲取數據!");
return value;
}
boolean lock = false;
try {
// putIfAbsent 等同於redis setnx,存在則返回,不存在則插入
lock = mapLock.putIfAbsent(key, key) == null;//插入一條數據,判斷如果不存在則插入並返回null,存在則獲取舊值返回。如果返回null,意味着插入成功,獲得鎖
if (lock) {//拿到鎖
value = redisService.get(key);// 二次校驗
if(value != null){
System.out.println("從緩存中獲取數據!");
return value;
}
// 2.從數據庫中獲取數據
value = dataService.get(key);
System.out.println("從數據庫中獲取數據!");
// 3.將數據插入緩存
redisService.set(key, value, 5*60);
}else {// 沒拿到鎖怎麼辦?--緩存降級
// 1、重試,爭搶鎖,控制重試次數
// 2、返回空
// 3、備用緩存
value = null;
System.out.println("緩存降級!");
}
return value;
} finally{
if (lock) {
mapLock.remove(key);//釋放鎖
}
}
}
優點:減小鎖的顆粒度
缺點:鎖的顆粒度仍然大,同一個key,只有一個請求能獲得鎖,其餘全部阻塞。