什麼是緩存的擊穿
緩存穿透是指緩存和數據庫中都沒有的數據,緩存每次都無法命中,因爲我們默認不會緩存null 值,導致用戶訪問 id=-1 這樣的數據時,一直都無法命中,這時的用戶很可能是攻擊者,攻擊會導致數據庫壓力過大。
解決方法:
A.在redis緩存null, 當它再次查詢id = -i ,在緩存裏面有值,可以不在訪問數據庫
但是缺點緩存了null 值
B.使用BloomFilter
Bloom Filter是一個佔用空間很小、效率很高的隨機數據結構,它由一個bit數組和一組Hash算法構成。可用於判斷一個元素是否在一個集合中,查詢效率很高(1-N,最優能逼近於1)。
在很多場景下,我們都需要一個能迅速判斷一個元素是否在一個集合中
1.判斷500w條(url)裏面是否包含一個url地址;
2.從數十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱;
3.將已存在的緩存放到布隆中,當黑客訪問不存在的緩存時迅速返回避免緩存及DB掛掉
用戶訪問,先經過一個集合,集合裏面有所有的數據標識,當集合裏面有標識,代表數據庫裏面也有標識(判斷一個數據在集合裏面有沒有)
a.當集合裏面有,緩存裏面有,返回緩存的值
b.緩存裏面沒有,查詢數據庫。
方案:
1 .使用HashSet 的唯一唯一性,去重
HashSet dataLabel = new HashSet();
dataLabel.add();
dataLable.add();
當數據量很大時,它的內存佔用大不大 ?非常大
一個地址16字節,10億即可達到上百G的內存。HashSet效率逼近O(1),數據庫就不談效率了,不在一個數量級。
2.將地址用MD5算法或其他單向映射算法計算後存入HashSet,無論地址多大,保存的只有MD5後的固定位數
,佔用空間小於存儲完整信息,存在衝突的可能(非垃圾郵箱可能MD5後和某垃圾郵箱一樣,概率低)
3…布隆過濾器,將所有地址經過多個Hash算法,映射到一個bit數組
所有地址經過Hash後映射到同一個bit數組,看清了,只有一個超大的bit數組,保存所有的映射,佔用空間極小,衝突概率高。
大家知道,java中的HashMap有個擴容參數默認是0.75,也就是你想存75個數,至少需要一個100的數組,而且還會有不少的衝突。實際上,Hash的存儲效率是0.5左右,存5個數需要10個的空間。算起來佔用空間還是挺大的。
而布隆過濾器就不用爲每個數都分配空間了,而是直接把所有的數通過算法映射到同一個數組,帶來的問題就是衝突上升,只要概率在可以接受的範圍,用時間換空間,在很多時候是好方案。布隆過濾器需要的空間僅爲HashMap的1/8-1/4之間,而且它不會漏掉任何一個在黑名單的可疑對象,問題只是會誤傷一些非黑名單對象。
BoolmFilter
初始化狀態是一個全爲0的bit數組
數據存儲的容器時bit 集合BitSet
當容量很小,存儲數據很多時,hash 容易碰撞,具體時計算處理的pos 值,一樣了,導致的,通過增加hash 函數的個數來減少碰撞。
如何使用位集合來判斷一個數據是否存在
實現Boolmfilter
public class BloomFilter {
// 1 初始化一個位集合
public static int length = Integer.MAX_VALUE;
public static BitSet sets = new BitSet(length);
// 準備hash hash
public static int hash_length = 5 ;
public static int []sends = new int[] {17,19,29,31,37};
public static HashFun []funs = new HashFun[hash_length];
static {
for (int i = 0; i < hash_length; i++) {
funs[i] = new HashFun(sends[i]);
}
}
public static void add(String key) {
for (HashFun hashFun : funs) {
int hashCode = hashFun.hash(key); // 計算hash 函數
int pos = hashCode & (length-1); // 映射在位集合上的位置
System.out.println("位置有:"+ pos);
sets.set(pos, true);
}
}
public static boolean has(String key) {
for (HashFun hashFun : funs) {
int hashCode = hashFun.hash(key); // 計算hash 函數
int pos = hashCode & (length-1); // 映射在位集合上的位置
if(!sets.get(pos)) {
return false ;
}
}
return true;
}
public static void main(String[] args) {
add("mayun");
System.out.println("----------------");
add("mahuateng");
if(has("mayun")) {
System.out.println("ok");
}else {
System.out.println("error");
}
if(has("mahuateng")) {
System.out.println("ok");
}
}
}
public class HashFun {
private int send ;
public HashFun(int send) {
this.send = send ;
}
public int hash(String key) {
int h = 0;
char []value = key.toCharArray();
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = this.send * h + val[i];
}
}
return h;
}
}