1.緩存穿透
是指客戶端請求的數據在緩存中和數據庫中都不存在,這樣緩存永遠不會生效,導致請求直接穿透緩存到達數據庫,給數據庫帶來壓力的情況。
常見的解決方案有兩種:
緩存空對象:實現簡單,維護方便;但產生了額外的內存消耗(可以給該對象設置一個很短的 TTL ,時間一到,就被清除了),可能造成短期的不一致(在緩存空對象的有效期內,數據庫中新增了該數據,但緩存中的空值尚未過期,導致客戶端仍然獲取到舊的空值,直到緩存過期后才會更新為真實數據。)。
布隆過濾:內存占用少,沒有多余key;但實現復雜,存在誤判可能。
方案一比較常用,這里是該方法的實現:
public Result queryById(Long id) {
? ? ? ? String key = CACHE_SHOP_KEY + id;
? ? ? ? //1.從redis中查詢店鋪緩存
? ? ? ? String shopJson = stringRedisTemplate.opsForValue().get(key);
? ? ? ? //2.判斷是否存在
? ? ? ? if (StrUtil.isNotBlank(shopJson)) {//只有 返回字符串 才為真;null,空字符串,換行都會返回false
? ? ? ? ? ?//3.存在,直接返回
? ? ? ? ? ?Shop shop = JSONUtil.toBean(shopJson, Shop.class);//字符串轉成shop對象
? ? ? ? ? ?return Result.ok(shop);
? ? ? ? ?}
? ? ? ? //判斷命中的是否是空值
? ? ? ? if (shopJson != null){
? ? ? ? ? ? //返回一個錯誤信息
? ? ? ? ? ? return Result.fail("店鋪信息不存在!");
? ? ? ? }
? ? ? ? //4.不存在,根據id查詢數據庫
? ? ? ? Shop shop = getById(id);//shop對象
? ? ? ? //5.不存在,返回錯誤
? ? ? ? if (shop == null) {
? ? ? ? ? ? //緩存穿透
? ? ? ? ? ? //將空值寫入redis
? ? ? ? ? ? //返回錯誤
? ? ? ????????????stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);? ? ? ? //shop對象轉成json形式存入
? ? ? ? ? ? return Result.fail("店鋪不存在!");
? ? ? ? }
? ? ? ? //6.存在,數據寫入redis
? ? ? ? ????????stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);????????//shop對象轉成json形式存入
? ? ? ? //7.返回
? ? ? ? return Result.ok(shop);}?
以上這兩種都屬于被動的方案,事實上我們可以主動的采取一些措施解決緩存穿透,比如:增加ID的復雜度,避免被猜測ID規律;做好數據的基礎格式校驗;加強用戶權限校驗;做好熱點參數的限流。
2.緩存雪崩
緩存雪崩是指在同一時間段內大量緩存數據同時失效(過期)或者Redis服務宕機,導致大量請求瞬間穿透緩存層,直接沖擊后端數據庫,造成數據庫負載驟增甚至崩潰,最終引發系統整體不可用的現象。
解決方案:
- 給不同的Key的TTL添加隨機值
- 利用Redis集群提高服務的可用性
- 給緩存業務添加降級限流策略
- 給業務添加多級緩存
3.緩存擊穿
該問題也叫做熱點Key問題,就是一個被高并發訪問并且緩存重建業務較復雜的Key突然失效了,無數的請求訪問會在瞬間給數據庫帶來巨大的沖擊。
解決方案:
互斥鎖:沒有額外的內存消耗,保證了一致性,實現比較簡單;但是線程需要等待,性能受影響,可能有死鎖風險。
邏輯過期:線程無需等待,性能較好;但是不保證一致性,有額外的內存消耗(存儲過期時間),實現復雜。