高併發下的快取失效問題
阿新 • • 發佈:2022-03-20
快取擊穿
- 對於一些設定了過期時間的 key, 如果這些 key 可能還在某些時間點被超高併發地訪問,是一種非常熱點的資料(比如: 某某明星吃瓜事件等)
- 如果這個 key 在大量請求進來時正好失效,那麼這些 key 就會穿過快取區訪問資料庫
- 解決方法:加併發鎖,在大量併發請求下,只讓一個人去查,而其他人進行等待,查到之後釋放鎖,其他人再去獲取鎖,先查快取,就會有資料,不用去 db
- 在單體應用中加鎖,使用 synchronized
sychronized(this){ String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");if(!StringUtils.isEmpty(catalogJSON)){ // 加鎖後,再次查詢快取 // 如果快取不為 null, 直接返回 Map<String, List<CatalogVo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, Object>>(){}); // 查到的結果,應該立即放入快取,以防鎖釋放後,其他執行緒獲取鎖,但是從快取中還沒讀到資料,又會再次查詢資料庫,導致的時序問題帶來的查了兩次資料庫問題String s = JSON.toJSONString(catalogJsonFromDb); redisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit.DAYS); return result; }
快取穿透
- 在快取中查詢一個不存在的資料,由於快取沒有被命中,就會去查詢資料庫,資料庫中也不存在此條記錄,那麼每次的請求都會越過快取區查詢資料庫,快取就失去了作為快取的意義。
- 帶來的問題:
- 當請求併發量很大的時候,資料庫的壓力增大的,最終可能導致崩潰
- 解決方法:
- 將該查詢的key ,給它一個 value 為 null 的值,並加入短暫的過期時間,以防後面資料庫有該值了,這個資料一直佔著。
-
// 從快取中獲取資料 String catalogJson = redisTemplate.opsForValue().get("catalogJSON"); // 判斷資料是否是空的 if(StringUtils.isEmpty(catalogJSON)){ // 如果為空,那麼查詢資料庫 Map<String, List<CatalogVO>> catalogJsonFromDb = getCatalogJsonFromDb(); // 將查到的資料再放入快取,將物件轉為 json 放在快取中 String s = JSON.toJSONString(catalogJsonFromDb); redisTemplate.opsForValue().set("catalogJSON", 0); return catalogJsonFromDb; }
快取雪崩
- 設定快取時 key 採用相同的過期時間,導致快取在某一時刻同時失效,請求因此進入資料庫,導致資料庫壓力過大。
- 解決方法: 設定的過期時間儘量不要一樣,可以在一個基礎的失效時間上增加一個隨機值(1-5 分鐘左右), 這樣可以有效避免快取大量失效。
-
// 從快取中獲取資料 String catalogJson = redisTemplate.opsForValue().get("catalogJSON"); // 判斷資料是否是空的 if(StringUtils.isEmpty(catalogJSON)){ // 如果為空,那麼查詢資料庫 Map<String, List<CatalogVO>> catalogJsonFromDb = getCatalogJsonFromDb(); // 將查到的資料再放入快取,將物件轉為 json 放在快取中 String s = JSON.toJSONString(catalogJsonFromDb); redisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit,DAYS); // 時間按實際需要定義 return catalogJsonFromDb; }