Redis緩存穿透、緩存擊穿和緩存雪崩


 

前言

作為一種非關系型數據庫,redis也總是免不了有各種各樣的問題,這篇文章主要是針對其中三個問題進行講解:緩存穿透、緩存擊穿和緩存雪崩,並給出一些解決方案。

一、緩存穿透

1、概念

緩存穿透是指查詢一個數據庫一定不存在的數據。正常的使用緩存流程大致是,數據查詢先進行緩存查詢,如果key不存在或者key已經過期,再對數據庫進行查詢,並把查詢到的對象,放進緩存。如果數據庫查詢對象為空,則不放進緩存。

這里需要注意緩存擊穿的區別,緩存擊穿,緩存擊穿是指緩存中沒有但數據庫中有的數據,並且某一個key非常熱點,在不停的扛着大並發,大並發集中對這一個點進行訪問,當這個key在失效的瞬間(一般是緩存時間到期),持續的大並發就穿破緩存,直接請求數據庫,就像在一個屏障上鑿開了一個洞。

為了避免緩存穿透其實有很多種解決方案。下面介紹幾種

2、解決方案

(1)布隆過濾器

布隆過濾器是一個bit向量或者bit,如果我們要映射一個值到布隆過濾器中,我們使用多個不同的哈希函數生成多個哈希值,並將每個生成的哈希值指向的bit位設置為1,如下baidu一詞設置了三個位置為1。

原理:對一個key進行k個hash算法獲取k個值,在比特數組中將這k個值散列后設定為1,然后查的時候如果特定的這幾個位置都為1,那么布隆過濾器判斷該key存在。

“tencent”一詞,對應的情況

可以看到,不同的詞對應的bit位置可能相同,當詞很多的情況時,可能大部分bit位置都是1,這時查詢taobao可能對應的位置都為1,只能說明taobao一詞可能存在,不是一定存在的,這時1就被覆蓋了,這就是布隆過濾器的誤判如果它說不存在那肯定不存在,如果它說存在,那數據有可能實際不存在。

Redis的bitmap只支持2^32大小,對應到內存也就是512MB,誤判率萬分之一,可以放下2億左右的數據,性能高,空間占用率及小,省去了大量無效的數據庫連接。

因此我們可以通過布隆過濾器,將Redis緩存穿透控制在一個可容范圍內。

使用布隆過濾器:
導入依賴

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>

代碼:

public class Test {

private static int size = 1000000;//預計要插入多少數據

private static double fpp = 0.01;//期望的誤判率

private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

public static void main(String[] args) {
//插入數據
for (int i = 0; i < 1000000; i++) {
bloomFilter.put(i);
}
int count = 0;
for (int i = 1000000; i < 2000000; i++) {
if (bloomFilter.mightContain(i)) {
count++;
System.out.println(i + "誤判了");
}
}
System.out.println("總共的誤判數:" + count);
}
}

應用:

@Cacheable(value="key1")
public String get(String key) {
String value = redis.get(key);
// redis中不存在該緩存
if (value == null) {
//布隆過濾器也沒有,直接返回
if(!bloomfilter.mightContain(key)){
return null;
}else{
//布隆過濾器中能查到,不代表一定有,查出來放入redis,同樣也可以避免緩存穿透
value = db.get(key);
redis.set(key, value);
}
}
return value;
}

(2)、緩存空對象
當存儲層不命中后,即使返回的空對象也將其緩存起來,同時會設置一個過期時間,之后再訪問這個數據將會從緩存中獲取,保護了后端數據源。

但是這種方法會存在兩個問題

 ● 如果空值能夠被緩存起來,這就意味着緩存需要更多的空間存儲更多的鍵,因為這當中可能會有很多的空值的鍵;

 ● 即使對空值設置了過期時間,還是會存在緩存層和存儲層的數據會有一段時間窗口的不一致,這對於需要保持一致性的業務會有影響。

二、緩存雪崩

(1)、概念
緩存雪崩是指緩存中大批量數據到過期時間,而查詢數據量巨大,引起數據庫壓力過大甚至down機。和緩存擊穿不同的是,緩存擊穿指並發查同一條數據,緩存雪崩是不同數據都過期了,很多數據都查不到從而查數據庫。

產生雪崩的原因之一,假如馬上就要到雙十一零點,很快就會迎來一波搶購,這波商品時間比較集中的放入了緩存,假設緩存一個小時。那么到了凌晨一點鍾的時候,這批商品的緩存就都過期了。而對這批商品的訪問查詢,都落到了數據庫上,對於數據庫而言,就會產生周期性的壓力波峰。

博主在做電商項目的時候,一般有三種方法:
(1)采取不同分類商品,緩存不同周期。在同一分類中的商品,加上一個隨機因子。這樣能盡可能分散緩存過期時間,而且,熱門類目的商品緩存時間長一些,冷門類目的商品緩存時間短一些,也能節省緩存服務的資源。
(2)如果緩存數據庫是分布式部署,將 熱點數據均勻分布在不同的緩存數據庫中。
(3)設置熱點數據永遠不過期。

(4) 使用加鎖限流的方式


三、緩存擊穿

(1)概念
緩存擊穿,是指緩存中沒有但數據庫中有的數據,並且某一個key非常熱點,在不停的扛着大並發,大並發集中對這一個點進行訪問,當這個key在失效的瞬間(一般是緩存時間到期),持續的大並發就穿破緩存,直接請求數據庫,就像在一個屏障上鑿開了一個洞。

(2)解決方案

 ● 設置熱點數據永遠不過期。

 ● 使用互斥鎖(mutex key)
  業界比較常用的做法,是使用mutex。簡單地來說,就是在緩存失效的時候(判斷拿出來的值為空),不是立即去load db,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,當操作返回成功時,再進行load db的操作並回設緩存;否則,就重試整個get緩存的方法。

SETNX,是「SET if Not eXists」的縮寫,也就是只有不存在的時候才設置,可以利用它來實現鎖的效果。

public String get(key) {
String value = redis.get(key);
if (value == null) { //代表緩存值過期
//設置3min的超時,防止del操作失敗的時候,下次緩存過期一直不能load db
if(redis.setnx(key_mutex,1,3*60)==1){//代表設置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
}else{//這個時候代表同時候的其他線程已經load db並回設到緩存了,這時候重試獲取緩存值即可
sleep(50);
get(key);//重試
}
}else{
return value;
}
}

有收獲,就在看 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM