分佈式架構中redis緩存穿透系統解決方案

理解概念

先了解決緩存的應用場景
Redis提供持久化的功能,保證數據的恢復機制。
Redis提供成熟的主備同步,故障切換的功能,從而保證了高可用性。
Redis的集羣策略實現了負載均衡。
在這裏插入圖片描述

緩存穿透是指查詢一個一定不存在的數據,由於緩存是不命中所以會查詢數據庫,這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。
在這裏插入圖片描述

緩存擊穿是指緩存中某個熱點key在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的併發請求過來,這些請求發現緩存過期一般都會從後端DB加載數據並回設到緩存,這個時候大併發的請求可能會瞬間把後端DB壓垮。
在這裏插入圖片描述

緩存雪崩是指在我們設置緩存時採用了相同的過期時間,導致緩存在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重雪崩。
在這裏插入圖片描述

代碼演示問題

使用代碼演示緩存穿透的問題,代碼如下:

package cn.tx.service.impl;

import cn.tx.dao.UserDao;
import cn.tx.domain.Result;
import cn.tx.domain.User;
import cn.tx.service.UserService;
import cn.tx.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 騰訊課程搜索 拓薪教育
 * 櫻木老師
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 通過主鍵查詢用戶
     * @param id
     * @return
     */
    @Override
    public Result selectUserById(Integer id) {
        // 先從緩存中查詢
        Object object = redisUtil.getCacheObject(String.valueOf(id));
        // 如果能查詢到,直接返回
        if(object != null){
            return new Result("ok",200,object);
        }

        // 緩存中查詢不到,查詢數據庫
        User user = userDao.selectUserById(id);
        // 傳入到緩存中
        if(user != null){
            // 存入到redis緩存中,5秒鐘過期時間
            redisUtil.setCacheObject(String.valueOf(id),user,5);
            return new Result("ok",200,user);
        }

        // 查詢無果
        return new Result("查詢無果",500,new NullValueResult());
    }

}

布隆過濾器原理

布隆過濾器是一個 bit 向量或者說 bit 數組,長這樣。
在這裏插入圖片描述

如果我們要映射一個值到布隆過濾器中,我們需要使用多個不同的哈希函數生成多個哈希值,並對每個生成的哈希值指向的 bit 位置 1,例如針對值 “baidu” 和三個不同的哈希函數分別生成了哈希值 1、4、7,則上圖轉變爲:
在這裏插入圖片描述

Ok,我們現在再存一個值 “tencent”,如果哈希函數返回 3、4、8 的話,圖繼續變爲:
在這裏插入圖片描述

值得注意的是,4 這個 bit 位由於兩個值的哈希函數都返回了這個 bit 位,因此它被覆蓋了。現在我們如果想查詢 “dianping” 這個值是否存在,哈希函數返回了 1、5、8三個值,結果我們發現 5 這個 bit 位上的值爲 0,說明沒有任何一個值映射到這個 bit 位上,因此我們可以很確定地說 “dianping” 這個值不存在。而當我們需要查詢 “baidu” 這個值是否存在的話,那麼哈希函數必然會返回 1、4、7,然後我們檢查發現這三個 bit 位上的值均爲 1,那麼我們可以說 “baidu” 存在了麼?答案是不可以,只能是 “baidu” 這個值可能存在。
這是爲什麼呢?答案跟簡單,因爲隨着增加的值越來越多,被置爲 1 的 bit 位也會越來越多,這樣某個值 “taobao” 即使沒有被存儲過,但是萬一哈希函數返回的三個 bit 位都被其他值置位了 1 ,那麼程序還是會判斷 “taobao” 這個值存在。
過小的布隆過濾器很快所有的 bit 位均爲 1,那麼查詢任何值都會返回“可能存在”,起不到過濾的目的了。布隆過濾器的長度會直接影響誤報率,布隆過濾器越長其誤報率越小。
另外,哈希函數的個數也需要權衡,個數越多則布隆過濾器 bit 位置位 1 的速度越快,且布隆過濾器的效率越低;但是如果太少的話,那我們的誤報率會變高。

演示布隆過濾器
在這裏插入圖片描述

常見的應用場景,利用布隆過濾器減少磁盤 IO 或者網絡請求,因爲一旦一個值必定不存在的話,我們可以不用進行後續昂貴的查詢請求。

設置的容錯率越小,布隆過濾器的數組越大,hash函數越多。可以根據源代碼查看。
在這裏插入圖片描述

用布隆過濾器解決緩存穿透的問題

具體的代碼如下:

package cn.tx.service.impl;

import cn.tx.dao.UserDao;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.List;

/**
 * 騰訊課程搜索 拓薪教育
 * 櫻木老師
 */
@Service
public class BloomFilterUtil {

    @Autowired
    private UserDao userDao;

    // 定義預存數據的個數
    private static int size = 1000000;

    // 初始化布隆過濾器對象   0.01表示容錯率
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(),size,0.01);

    /**
     * 初始化的方法
     */
    @PostConstruct
    public void init(){
        // 查詢數據庫中所有的id值
        List<Integer> list = userDao.selectAllIds();
        for (Integer id : list) {
            // 存入到布隆過濾器中
            bloomFilter.put(id);
        }
    }

    /**
     * 布隆過濾器,判斷id是否可能存在
     * @param id
     * @return
     */
    public boolean mightContain(Integer id){
        return bloomFilter.mightContain(id);
    }

}


修改業務層的代碼
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private BloomFilterUtil bloomFilterUtil;

    /**
     * 採用布隆過濾器的方式來解決緩存穿透
     * @param id
     * @return
     */
    @Override
    public Result selectUserById(Integer id) {
        // 先從緩存中查詢
        Object object = redisUtil.getCacheObject(String.valueOf(id));
        // 如果能查詢到,直接返回
        if(object != null){
            // 返回正常的數據
            return new Result("ok",200,object);
        }else{
            // 去布隆過濾器中判斷該id是否存在
            boolean b = bloomFilterUtil.mightContain(id);
            if(!b){
                return new Result("查詢無果",500,new NullValueResult("無數據"));
            }
        }

        // 緩存中查詢不到,查詢數據庫
        User user = userDao.selectUserById(id);
        // 傳入到緩存中
        if(user != null){
            // 存入到redis緩存中,5秒鐘過期時間
            redisUtil.setCacheObject(String.valueOf(id),user,5);
            return new Result("ok",200,user);
        }
        // 查詢無果
        return new Result("查詢無果",500,new NullValueResult("無數據"));
    }
}


學習更多架構免費課程請關注:java架構師免費課程
每晚20:00直播分享高級java架構技術
掃描加入QQ交流羣264572737
在這裏插入圖片描述
進入羣內免費領取海量java架構面試題
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章