理解概念
先了解決緩存的應用場景
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架構面試題