最近在作訂單緩存查詢相關需求,記錄下該過程當中緩存查詢考慮的幾個問題以及處理方案。數據庫
實際場景中使用緩存都是先去緩存中查詢,若是緩存沒有命中,在去查詢數據庫並將結果緩存。若是查詢一個在系統中根本就不存在的數據,就會形成每次請求都會穿透緩存去查詢數據庫。若是出現大量的緩存穿透(或者惡意攻擊),就會對數據庫形成比較大的壓力。緩存
對於數據庫中不存在的數據,存儲特定的值表示數據不存在。在發生insert以後將緩存中對應數據移除,避免在數據生成以後緩存中查詢仍是NULL。併發
1 public Order findById(Long orderId) { 2 // 從數據庫查詢 3 Order order = getOrderFromDB(orderId); 4 if (order == null) { 5 // 訂單不存在時候填入特定"NULL"表示訂單不存在 6 putOrderToCache(orderId, "NULL"); 7 } else { 8 putOrderToCache(orderId, order); 9 } 10 return order; 11 }
在查詢同一併發量較大狀況下,若是該訂單緩存失效,就會形成這一瞬間全部的訂單查詢請求都會訪問到數據庫,形成數據庫壓力增大。spa
併發查詢同一訂單,緩存中不存在,加鎖查詢數據庫,其他請求等待獲取鎖或者獲取超時返回數據或者查詢數據庫。線程
public Order findById(Long orderId) { // 從緩存中獲取 Result result = getOrderFromCache(orderId); Order order = result.getOrder(); if (result.isEmpty()) { // 從DB獲取數據 order = getOrderFromDB(orderId); // 寫入緩存 putOrderToCache(orderId, order); } return order; }
上面這種寫法,通常是沒什麼問題,在併發量大時候會形成全部請求都查詢到數據庫。併發100個請求查詢同一個訂單,當緩存沒數據時100請求都會到DB。爲了處理這種狀況,解決的方式就是加鎖。code
public Order findById(Long orderId) { // 從緩存中獲取 Result result = getOrderFromCache(orderId); Order order = result.getOrder(); if (result.isEmpty()) { try { if (lock.tryLock(1)) { // 從DB獲取數據 order = getOrderFromDB(orderId); // 寫入緩存 putOrderToCache(orderId, order); } else { Thread.sleep(10);//10毫秒根據業務場景自定義 return findById(orderId); } } catch (Exception e) { // } finally { lock.unlock(); } } return order; }
嘗試獲取一次鎖,併發狀況下,獲取不到鎖就從新走一遍流程(先查緩存,在查DB),加鎖時間不要設置太長,避免過多的線程在等待。採用這種方式,當有100個請求併發查詢時候,一個線程拿到鎖查詢DB,剩餘99個線程在等待10毫秒重試,好比查詢DB線程獲取到數據須要耗時5毫秒,10毫秒以後99個線程發現緩存中有值,直接從緩存中取值,耗時10毫秒。blog
爲了不緩存中的數據愈來愈多或者緩存一些不多用到或者根本不會使用到數據,一般都會在訂單緩存中加入過時時間,好比幾分鐘。當某些狀況下,會出現多個訂單的過時時間是同樣的,即多個訂單緩存數據同時失效。get
對於每一個訂單的緩存失效時間都不同,失效時間都是一個範圍內隨機值,能夠在必定程度上減小緩存同時失效io