但是,事無完美,在高并發環境下,所有的case都會極端化,如果這是一個十分龐大的集合(給這個龐大一個具體的值吧,一個億),簡單的一個hash map,不考慮連結清單所需的指針記憶體空間,一億個int類型的整數,就需要380多M(4byte × 10 ^8),十億的話就是4個G,不考慮性能,光算算這記憶體開銷,即使現在滿地都是128G的伺服器,也不好吃下這一壺。
bitmap則使用位數代表數的大小,bit中存儲的0或者1來辨別該整數是否存在,具體模型如下:

“bitmap”,其中4321這四個數存在
計算一下bitmap的記憶體開銷,如果是1億以内的資料查找,我們隻需要1億個bit = 12MB左右的記憶體空間,就可以完成海量資料查找了,是不是極其誘人的一個記憶體縮減,以下為Java實作的bitmap代碼:
public class MyBitMap {
private byte[] bytes;
private int initSize;
public MyBitMap(int size) {
if (size <= 0) {
return;
}
initSize = size / (8) + 1;
bytes = new byte[initSize];
}
public void set(int number) {
//相當于對一個數字進行右移動3位,相當于除以8
int index = number >> 3;
//相當于 number % 8 擷取到byte[index]的位置
int position = number & 0x07;
//進行|或運算 參加運算的兩個對象隻要有一個為1,其值為1。
bytes[index] |= 1 << position;
}
public boolean contain(int number) {
int index = number >> 3;
int position = number & 0x07;
return (bytes[index] & (1 << position)) != 0;
}
public static void main(String[] args) {
MyBitMap myBitMap = new MyBitMap(32);
myBitMap.set(30);
myBitMap.set(13);
myBitMap.set(24);
System.out.println(myBitMap.contain(2));
}
}
使用簡單的byte數組和位運算,就能做到時間與空間的完美均衡,是不是美美哒,wrong!試想一下,如果我們明确這是一個一億以内,但是數量級隻有10的集合,我們使用bitmap,同樣需要開銷12M的資料,如果是10億以内的資料,開銷就會漲到120M,bitmap的空間開銷永遠是和他的資料取值範圍挂鈎的,隻有在海量資料下,他才能夠大顯身手。
再說說剛剛提到的那個極端case,假設這個資料量在一千萬,但是取值範圍好死不死就在十個億以内,那我們不可避免還是要面對120M的開銷,有方法應對麼?
布隆過濾器
如果面對筆者說的以上問題,我們結合一下正常的解決方案,譬如說hash一下,我将十億以内的某個資料,hash成一億内的某個值,再去bitmap中查怎麼樣,如下圖,布隆過濾器就是這麼幹的:
到的值,減小hash碰撞的機率
像上面的圖注所說,我們可以利用多個hash算法減小碰撞機率,但隻要存在碰撞,就一定會有錯誤判斷,我們無法百分百确定一個值是否真的存在,但是hash算法的魅力在于,我不能确定你是否存在,但是我可以确定你是否真的不存在,這也就是以上的實作為什麼稱之“過濾器”的原因了。
高并發緩存設計政策
why cache??
如果讀者是一個計算機專業的同學,cache這個詞應該是能達到讓耳朵起繭的出現頻次。在計算機體系中,cache是介于cpu以及記憶體之間,用來緩和cpu和記憶體處理速度差距的那麼一個和事佬;在OS中,page cache又是記憶體和IO之間的和事佬。
cache是個和事老??聽着似乎怪怪的,但是也蠻形象的啦。
前面講了大半截的算法理論,為了防止讀者犯困,直接進入下半部分主題,高并發緩存設計。
即使是在軟體層,我們同樣需要這麼一個和事老,從最簡單的服務架構開始,通常我們在服務端發起請求,然後CURD某個關系型資料庫例如Mysql。但是,類似這樣的架構都需要有一個磁盤作為終端持久化,即使增加索引,使用B+樹的這種資料結構進行優化查詢,效率還是會卡在需要頻繁尋道的IO上。
這個時候,一個和事老的作用就十分明顯了,我們會添加一些記憶體操作,來緩和IO處理速度慢帶來的壓力。cache is not a problem,how to use it is actually a problem。
緩存一緻性問題
緩存處理的機制有以下幾種:
cache aside;
read through;
write through;
write behind caching;
緩存穿透問題
所謂的緩存擊穿,就是當請求發出,而無法在緩存中讀到資料時,請求還是會作用到database,這樣的話,緩存減壓的效果就不複存在了。
設想這麼一個場景,如果一個使用者,使用大流量惡意頻繁地去查詢一條資料庫中沒有的記錄,一直擊穿緩存,勢必會把database打死,如何避免緩存擊穿,這就是一個問題了。
有兩種方案,第一種,在緩存中添加空值,如果在database中查詢無果,我們大可以把值設定為null,防止下次再次通路資料庫,這樣做簡單便捷,但是多少有些浪費空間。
第二種方案,就是使用布隆過濾器(點題),在cache與web伺服器中間加一層布隆過濾器,對通路的key做記錄,如此以來,同樣可以解決緩存擊穿的問題。
緩存雪崩問題
緩存雪崩發生于在某個時間點,緩存同時失效,例如緩存設定了失效時間,這會關聯的導緻大量緩存擊穿問題。
加分布式鎖是一種解決方案,隻有拿到鎖的請求才能通路database。但是這樣治标不治本,當請求量過多時,大量的線程阻塞,也會把記憶體撐壞的。
預熱資料,分散地設定失效時間,這樣可以減少緩存雪崩發生的機率。
提高緩存可用性,cache的單點一樣是會是緩存雪崩的隐患,大部分緩存中間件都提供高可用架構,如redis的主從+哨兵架構。
原文連結:
https://blog.csdn.net/that_is_cool/article/details/91346356版權聲明:本文為CSDN部落客「that_is_cool」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。