幫你解讀什麼是Redis緩存穿透、緩存擊穿和緩存雪崩(包含解決方案)


前言
web

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

1、緩存穿透

一、概念redis

緩存穿透是指查詢一個數據庫必定不存在的數據。正常的使用緩存流程大體是,數據查詢先進行緩存查詢,若是key不存在或者key已通過期,再對數據庫進行查詢,並把查詢到的對象,放進緩存。若是數據庫查詢對象爲空,則不放進緩存。算法

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

爲了不緩存穿透其實有不少種解決方案。下面介紹幾種後端

二、解決方案數組

(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)、緩存空對象
當存儲層不命中後,即便返回的空對象也將其緩存起來,同時會設置一個過時時間,以後再訪問這個數據將會從緩存中獲取,保護了後端數據源。

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

 ● 若是空值可以被緩存起來,這就意味着緩存須要更多的空間存儲更多的鍵,由於這當中可能會有不少的空值的鍵;

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

2、緩存雪崩

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

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

博主在作電商項目的時候,通常有三種方法:
(1)採起不一樣分類商品,緩存不一樣週期。在同一分類中的商品,加上一個隨機因子。這樣能儘量分散緩存過時時間,並且,熱門類目的商品緩存時間長一些,冷門類目的商品緩存時間短一些,也能節省緩存服務的資源。
(2)若是緩存數據庫是分佈式部署,將 熱點數據均勻分佈在不一樣的緩存數據庫中。
(3)設置熱點數據永遠不過時。

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


3、緩存擊穿

(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;
}
}
本文來自個人博客: https://blog.csdn.net/qq_37989738

推薦閱讀

一、文末贈書 | 面試官居然又問我volatile

二、史上最全的Jackson框架使用教程

三、逐行解讀HashMap源碼【終章】

四、原創 | 萬萬沒想到,HashMap默認容量的選擇,居然背後有這麼多思考!?

掃描二維碼

獲取更多精彩

Java技術大聯盟

有收穫,就在看 

本文分享自微信公衆號 - Java技術大聯盟(jingdakunye520)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。