11.7、熱點(diǎn)key重建優(yōu)化

熱點(diǎn)key重建優(yōu)化

開發(fā)人員使用“緩存+過期時(shí)間”的策略既可以加速數(shù)據(jù)讀寫,又保證數(shù)據(jù)的定期更新,這種模式基本能夠滿足絕大部分需求。但是有兩個(gè)問題如果同時(shí)出現(xiàn),可能就會(huì)對(duì)應(yīng)用造成致命的危害:

  • 當(dāng)前key是一個(gè)熱點(diǎn)key(例如一個(gè)熱門的娛樂新聞),并發(fā)量非常大。

  • 重建緩存不能再短時(shí)間完成,可能是一個(gè)復(fù)雜計(jì)算,例如復(fù)雜的SQL、多次IO、多個(gè)依賴等。

在緩存失效的瞬間,有大量線程來重建緩存,造成后端負(fù)載過大,甚至可能會(huì)讓應(yīng)用崩潰。

要解決這個(gè)問題也不是很復(fù)雜,但是不能為了解決這個(gè)問題給系統(tǒng)帶來更多的麻煩,所以需要制定如下目標(biāo):

  • 減少重建緩存的次數(shù)。

  • 數(shù)據(jù)盡可能一致。

  • 較少的潛在危險(xiǎn)。

  1. 互斥鎖(mutex key)

    此方法只允許一個(gè)線程重建緩存,其他線程等待重建緩存的線程執(zhí)行完,重新從緩存獲取數(shù)據(jù)即可。

    下面代碼使用Redis的setnx命令實(shí)現(xiàn)上述功能:

    String get (String key) {
        //從Redis中獲取數(shù)據(jù)
        String value = redis.get(key);
        //如果value為空,則開始重構(gòu)緩存
        if (value == null) {
            //只允許一個(gè)線程重構(gòu)緩存,使用nx,并設(shè)置過期時(shí)間ex
            String mutexKey = "mutext:key:" + key;
            if (redis.set(mutexKey, "1", "ex 180", "nx")) {
                //從數(shù)據(jù)源獲取數(shù)據(jù)
                value = db.get(key);
                //回寫Redis,并設(shè)置過期時(shí)間
                redis.setex(key, timeout, value);
                //刪除 key_mutex
                redis.delete(mutexKey);
            }
            // 其他線程休息50毫秒后重試
            else {
                Thread.sleep(50);
                get(key);
            }
        }
        return value;
    }
    

    1)從Redis獲取數(shù)據(jù),如果值不為空,則直接返回值;否則執(zhí)行下面的2.1)和2.2)步驟。

    2.1)如果set(nx和ex)結(jié)果為true,說明此時(shí)已經(jīng)有其他線程正在執(zhí)行構(gòu)建緩存的工作,那么當(dāng)前線程將休息指定時(shí)間(例如這里是50毫秒,取決于構(gòu)建緩存的速度)后,重新執(zhí)行函數(shù),直到獲取到數(shù)據(jù)。

  2. 永遠(yuǎn)不過期

    “永遠(yuǎn)不過期”包含兩層意思

    • 從緩存層面來看,確實(shí)沒有設(shè)置過期時(shí)間,所以不會(huì)出現(xiàn)熱點(diǎn)key過期后產(chǎn)生的問題,也就是“物理”不過期。

    • 從功能層面來看,為每個(gè)value設(shè)置一個(gè)邏輯過期時(shí)間,當(dāng)發(fā)現(xiàn)超過邏輯過期時(shí)間后,會(huì)使用單獨(dú)的線程去構(gòu)建緩存。

    從實(shí)戰(zhàn)看,此方法有效杜絕了熱點(diǎn)key產(chǎn)生的問題,但唯一不足的就是重構(gòu)緩存期間,會(huì)出現(xiàn)數(shù)據(jù)不一致的情況,這取決于應(yīng)用方是否容忍這種不一致。下面代碼使用使用Redis進(jìn)行模擬:

    String get (final String key) {
        V v = redis.get(key);
        String value = v.getValue();
        //邏輯過期時(shí)間
        long logicTimeout = v.getLogicTimeout();
        //如果邏輯過期時(shí)間小于當(dāng)前時(shí)間,開始后臺(tái)構(gòu)建
        if (v.logicTimeout <= System.currentTimeMillis()) {
            String mutexKey = "mutex:key:" + key;
            if (redis.set(mutexKey, "1", "ex 180", "nx")) {
                //重構(gòu)緩存
                threadPool.execute(new Runnable(){
                    public void run () {
                        String dbValue = db.get(key);
                        redis.set(key, (dbValue, newLogicTiemout));
                        redis.delete(mutexKey);
                    }
                });
            }
        }
        return value;
    }
    

    作為一個(gè)并發(fā)量較大的應(yīng)用,在使用緩存時(shí)有三個(gè)目標(biāo):第一,加快用戶訪問速度,提高用戶體驗(yàn)。第二,降低后端負(fù)載,減少潛在風(fēng)險(xiǎn)。第三,保證數(shù)據(jù)“盡可能”及時(shí)更新。下面將按照這三個(gè)維度對(duì)上述解決方案進(jìn)行分析。

    • 互斥鎖(mutex key):這種方案思路比較簡(jiǎn)單,但是存在一定的隱患,如果構(gòu)建緩存過程出現(xiàn)問題或者時(shí)間較長(zhǎng),可能會(huì)存在思索和線程池阻塞的風(fēng)險(xiǎn),但是這種方法能夠較好地降低后端存儲(chǔ)負(fù)載,并在一致性上做得比較好。

    • “永遠(yuǎn)不過期”:這種方案由于沒有設(shè)置真正的過期時(shí)間,實(shí)際上已經(jīng)不存在熱點(diǎn)key產(chǎn)生的一系列危害,但是會(huì)存在數(shù)據(jù)不一致的情況,同時(shí)代碼復(fù)雜度會(huì)增大。

    下面是兩種解決方法的對(duì)比:

    解決方案 優(yōu)點(diǎn) 缺點(diǎn)
    簡(jiǎn)單分布式鎖 1.思路簡(jiǎn)單。2.保證一致性 1.代碼復(fù)雜度增大。2.存在死鎖的風(fēng)險(xiǎn)。3.存在線程池阻塞的風(fēng)險(xiǎn)
    “永遠(yuǎn)不過期” 基本杜絕熱點(diǎn)key問題 1.不保證一致性。2.邏輯過期時(shí)間增加代碼維護(hù)成本和內(nèi)存成本。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,527評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,656評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,853評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,103評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評(píng)論 1 281
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,487評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,815評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容