關(guān)于Redis的緩存擊穿問題

在談?wù)摼彺鎿舸┲鞍0龋覀兿葋砘貞浵聫木彺嬷屑虞d數(shù)據(jù)的邏輯,如下圖所示

image.png

因此,如果黑客每次故意查詢一個在緩存內(nèi)必然不存在的數(shù)據(jù),導(dǎo)致每次請求都要去存儲層去查詢咐容,這樣緩存就失去了意義毫缆。如果在大流量下數(shù)據(jù)庫可能掛掉重绷。這就是緩存擊穿银酗。

場景如下圖所示:


image.png

我們正常人在登錄首頁的時候,都是根據(jù)userID來命中數(shù)據(jù)良狈,然而黑客的目的是破壞你的系統(tǒng)后添,黑客可以隨機生成一堆userID,然后將這些請求懟到你的服務(wù)器上,這些請求在緩存中不存在薪丁,就會穿過緩存遇西,直接懟到數(shù)據(jù)庫上,從而造成數(shù)據(jù)庫連接異常。

解決方案:
在這里我們給出三套解決方案严嗜,大家根據(jù)項目中的實際情況粱檀,選擇使用.

講下述三種方案前,我們先回憶下redis的setnx方法

SETNX key value

將 key 的值設(shè)為 value 漫玄,當(dāng)且僅當(dāng) key 不存在茄蚯。

若給定的 key 已經(jīng)存在,則 SETNX 不做任何動作睦优。

SETNX 是『SET if Not eXists』(如果不存在渗常,則 SET)的簡寫。

可用版本:>= 1.0.0

時間復(fù)雜度: O(1)

返回值: 設(shè)置成功汗盘,返回 1皱碘。設(shè)置失敗,返回 0 隐孽。

效果如下

redis> EXISTS job                # job 不存在
(integer) 0

redis> SETNX job "programmer"    # job 設(shè)置成功
(integer) 1

redis> SETNX job "code-farmer"   # 嘗試覆蓋 job 尸执,失敗
(integer) 0

redis> GET job                   # 沒有被覆蓋
"programmer"

1家凯、使用互斥鎖

該方法是比較普遍的做法,即如失,在根據(jù)key獲得的value值為空時,先鎖上送粱,再從數(shù)據(jù)庫加載褪贵,加載完畢,釋放鎖抗俄。若其他線程發(fā)現(xiàn)獲取鎖失敗脆丁,則睡眠50ms后重試。

至于鎖的類型动雹,單機環(huán)境用并發(fā)包的Lock類型就行槽卫,集群環(huán)境則使用分布式鎖( redis的setnx)

集群環(huán)境的redis的代碼如下所示:

String get(String key) {  

   String value = redis.get(key);  

   if (value  == null) {  

    if (redis.setnx(key_mutex, "1")) {  

        // 3 min timeout to avoid mutex holder crash  

        redis.expire(key_mutex, 3 * 60)  

        value = db.get(key);  

        redis.set(key, value);  

        redis.delete(key_mutex);  

    } else {  

        //其他線程休息50毫秒后重試  

        Thread.sleep(50);  

        get(key);  

    }  

  }  

}  

優(yōu)點:思路簡單,保證一致性胰蝠。

缺點:代碼復(fù)雜度增大歼培,存在死鎖的風(fēng)險。

2茸塞、異步構(gòu)建緩存

在這種方案下躲庄,構(gòu)建緩存采取異步策略,會從線程池中取線程來異步構(gòu)建緩存钾虐,從而不會讓所有的請求直接懟到數(shù)據(jù)庫上噪窘。該方案redis自己維護一個timeout,當(dāng)timeout小于System.currentTimeMillis()時效扫,則進(jìn)行緩存更新倔监,否則直接返回value值。

集群環(huán)境的redis代碼如下所示:

String get(final String key) {  

        V v = redis.get(key);  

        String value = v.getValue();  

        long timeout = v.getTimeout();  

        if (v.timeout <= System.currentTimeMillis()) {  

            // 異步更新后臺異常執(zhí)行  

            threadPool.execute(new Runnable() {  

                public void run() {  

                    String keyMutex = "mutex:" + key;  

                    if (redis.setnx(keyMutex, "1")) {  

                        // 3 min timeout to avoid mutex holder crash  

                        redis.expire(keyMutex, 3 * 60);  

                        String dbValue = db.get(key);  

                        redis.set(key, dbValue);  

                        redis.delete(keyMutex);  

                    }  

                }  

            });  

        }  

        return value;  

    }

優(yōu)點:性價最佳菌仁,用戶無需等待

缺點:無法保證緩存一致性

3浩习、布隆過濾器

1、原理

布隆過濾器的巨大用處就是掘托,能夠迅速判斷一個元素是否在一個集合中瘦锹。因此他有如下三個使用場景:

1、網(wǎng)頁爬蟲對URL的去重闪盔,避免爬取相同的URL地址

2弯院、 反垃圾郵件,從數(shù)十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱(同理泪掀,垃圾短信)

3听绳、緩存擊穿,將已存在的緩存放到布隆過濾器中异赫,當(dāng)黑客訪問不存在的緩存時迅速返回避免緩存及DB掛掉椅挣。

OK头岔,接下來我們來談?wù)劜悸∵^濾器的原理

其內(nèi)部維護一個全為0的bit數(shù)組,需要說明的是鼠证,布隆過濾器有一個誤判率的概念峡竣,誤判率越低,則數(shù)組越長量九,所占空間越大适掰。誤判率越高則數(shù)組越小,所占的空間越小荠列。

假設(shè)类浪,根據(jù)誤判率,我們生成一個10位的bit數(shù)組肌似,以及2個hash函數(shù)((f_1,f_2))费就,如下圖所示(生成的數(shù)組的位數(shù)和hash函數(shù)的數(shù)量,我們不用去關(guān)心是如何生成的川队,有數(shù)學(xué)論文進(jìn)行過專業(yè)的證明)力细。

image

假設(shè)輸入集合為((N_1,N_2)),經(jīng)過計算(f_1(N_1))得到的數(shù)值得為2,(f_2(N_1))得到的數(shù)值為5呼寸,則將數(shù)組下標(biāo)為2和下表為5的位置置為1艳汽,如下圖所示


image

同理,經(jīng)過計算(f_1(N_2))得到的數(shù)值得為3对雪,(f_2(N_2))得到的數(shù)值為6河狐,則將數(shù)組下標(biāo)為3和下標(biāo)為6的位置置為1,如下圖所示

image

這個時候瑟捣,我們有第三個數(shù)(N_3)馋艺,我們判斷(N_3)在不在集合((N_1,N_2))中,就進(jìn)行(f_1(N_3)迈套,f_2(N_3))的計算

若值恰巧都位于上圖的紅色位置中捐祠,我們則認(rèn)為,(N_3)在集合((N_1,N_2))中
若值有一個不位于上圖的紅色位置中桑李,我們則認(rèn)為踱蛀,(N_3)不在集合((N_1,N_2))中

以上就是布隆過濾器的計算原理,下面我們進(jìn)行性能測試贵白,

2率拒、性能測試

代碼如下:

(1)新建一個maven工程,引入guava包

<dependencies>  

        <dependency>  

            <groupId>com.google.guava</groupId>  

            <artifactId>guava</artifactId>  

            <version>22.0</version>  

        </dependency>  

    </dependencies>

(2)測試一個元素是否屬于一個百萬元素集合所需耗時

package bloomfilter;

import com.google.common.hash.BloomFilter;

import com.google.common.hash.Funnels;

import java.nio.charset.Charset;

public class Test {

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

    public static void main(String[] args) {
        for (int i = 0; i < size; i++) {
            bloomFilter.put(i);
        }

        long startTime = System.nanoTime(); // 獲取開始時間

        //判斷這一百萬個數(shù)中是否包含29999這個數(shù)
        if (bloomFilter.mightContain(29999)) {
            System.out.println("命中了");
        }

        long endTime = System.nanoTime();   // 獲取結(jié)束時間
        System.out.println("程序運行時間: " + (endTime - startTime) + "納秒");
    }
}

輸出如下所示

命中了

程序運行時間: 219386納秒

也就是說禁荒,判斷一個數(shù)是否屬于一個百萬級別的集合猬膨,只要0.219ms就可以完成,性能極佳呛伴。

(3)誤判率的一些概念

首先勃痴,我們先不對誤判率做顯示的設(shè)置谒所,進(jìn)行一個測試,代碼如下所示

package bloomfilter; 

import java.util.ArrayList;
import java.util.List;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

public class Test {

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

    public static void main(String[] args) {
        for (int i = 0; i < size; i++) {
            bloomFilter.put(i);
        }

        List<Integer> list = new ArrayList<Integer>(1000);  
     
        //故意取10000個不在過濾器里的值沛申,看看有多少個會被認(rèn)為在過濾器里
        for (int i = size + 10000; i < size + 20000; i++) {  
            if (bloomFilter.mightContain(i)) {  
                list.add(i);  
            }  
        }  
        System.out.println("誤判的數(shù)量:" + list.size())劣领;
    }
}

輸出結(jié)果如下

誤判對數(shù)量:330

如果上述代碼所示,我們故意取10000個不在過濾器里的值铁材,卻還有330個被認(rèn)為在過濾器里剖踊,這說明了誤判率為0.03.即,在不做任何設(shè)置的情況下衫贬,默認(rèn)的誤判率為0.03。

下面上源碼來證明:

image

接下來我們來看一下歇攻,誤判率為0.03時固惯,底層維護的bit數(shù)組的長度如下圖所示

image

將bloomfilter的構(gòu)造方法改為

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

即,此時誤判率為0.01缴守。在這種情況下葬毫,底層維護的bit數(shù)組的長度如下圖所示

image

由此可見,誤判率越低屡穗,則底層維護的數(shù)組越長贴捡,占用空間越大。因此村砂,誤判率實際取值烂斋,根據(jù)服務(wù)器所能夠承受的負(fù)載來決定,不是拍腦袋瞎想的础废。

3汛骂、實際使用

redis偽代碼如下所示

String get(String key) {  

   String value = redis.get(key);  

   if (value  == null) {  
        if(!bloomfilter.mightContain(key)){
            return null;

        }else{
           value = db.get(key);  
           redis.set(key, value);  
        }

    }

    return value;

}

優(yōu)點:思路簡單评腺,保證一致性帘瞭,性能強。

缺點:代碼復(fù)雜度增大蒿讥,需要另外維護一個集合來存放緩存的Key蝶念,布隆過濾器不支持刪值操作

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芋绸,隨后出現(xiàn)的幾起案子媒殉,更是在濱河造成了極大的恐慌,老刑警劉巖侥钳,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件适袜,死亡現(xiàn)場離奇詭異,居然都是意外死亡舷夺,警方通過查閱死者的電腦和手機苦酱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門售貌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疫萤,你說我怎么就攤上這事颂跨。” “怎么了扯饶?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵恒削,是天一觀的道長。 經(jīng)常有香客問我尾序,道長钓丰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任每币,我火速辦了婚禮携丁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兰怠。我一直安慰自己梦鉴,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布揭保。 她就那樣靜靜地躺著肥橙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秸侣。 梳的紋絲不亂的頭發(fā)上存筏,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天,我揣著相機與錄音塔次,去河邊找鬼方篮。 笑死,一個胖子當(dāng)著我的面吹牛励负,可吹牛的內(nèi)容都是我干的藕溅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼继榆,長吁一口氣:“原來是場噩夢啊……” “哼巾表!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起略吨,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤集币,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后翠忠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鞠苟,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了当娱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吃既。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖跨细,靈堂內(nèi)的尸體忽然破棺而出鹦倚,到底是詐尸還是另有隱情,我是刑警寧澤冀惭,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布震叙,位于F島的核電站,受9級特大地震影響散休,放射性物質(zhì)發(fā)生泄漏媒楼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一戚丸、第九天 我趴在偏房一處隱蔽的房頂上張望匣砖。 院中可真熱鬧,春花似錦昏滴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至牺弄,卻和暖如春姻几,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背势告。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工蛇捌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咱台。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓络拌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親回溺。 傳聞我的和親對象是個殘疾皇子春贸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349