Redis的三個必知必會的問題

roc_guo發表於2022-07-20
導讀 快取的作用主要有兩個:一來提升訪問速度;二來保護資料庫。在業務量不大的時候,通常沒什麼大問題。但當業務量起來以後,如果快取使用不合理,三兄弟一定會如約而至,讓你體驗一下現實的殘酷。

Redis的三個必知必會的問題Redis的三個必知必會的問題
快取是網際網路應用中不可或缺的一部分。而提到快取,就不得不提它的三個經典問題——快取穿透、快取擊穿和快取雪崩,我稱它們為快取問題三兄弟。

快取的作用主要有兩個:一來提升訪問速度;二來保護資料庫。在業務量不大的時候,通常沒什麼大問題。但當業務量起來以後,如果快取使用不合理,三兄弟一定會如約而至,讓你體驗一下現實的殘酷。

三兄弟不來則已,一來輕則影響系統效能,重則直接拖垮資料庫,導致系統癱瘓。因此,我們不可掉以輕心,要防患於未然。

快取穿透

一個請求到達伺服器時,正常情況下是按照如下流程進行的。

沒遇到過這三個問題都不好意思說用過Redis

即按照如下步驟:

1.查詢快取,如果命中則返回。
2.快取未命中,則查詢資料庫。
3.將從資料庫中查詢到的資料寫入快取並返回。

如果每次都是這樣按部就班的處理,倒也相安無事。但是,凡事就怕但是。但是總會有例外,假如請求方對一個(資料庫中)根本不存在的資料進行訪問,那麼按照上面的流程,快取就形同虛設了。因為不存在,所以不會被寫入快取,這樣請求每次都會打到資料庫,這個現象就是所謂的「快取穿透」了。

如果只是因為個別請求去查詢不存在的資料,那其實也沒什麼大事。但快取穿透通常是伴隨一些「惡意請求」而來,通常是在短時間內湧入大量請求。如果放任不管,就等著資料庫當機吧。

如何解決

瞭解了導致快取穿透的原因,那麼解決方案也就明瞭了。可以從兩個方面下手:

  • 快取不存在的記錄。
  • 過濾不存在的請求。
  • 啥?不存在的記錄咋快取?其實很簡單,如果資料庫中也查不到,那就將快取的 value 設定成 null 即可(注意要根據業務特性設定合理的過期時間)。

    過濾不存在的請求,當一個請求到達伺服器,比如:

GET /api/user/1

過濾器會先判斷該資源是否存在,如果存在則放行,不存在則直接返回,從而起到保護系統的作用。

這種方式也有比較成熟的方案。比如布隆過濾器和布穀鳥過濾器(升級版布隆布隆過濾器)。

雙重加固

不管請求不存在的資源是有意還是無意,都不是我們想要的。所以,我們可以設定一個訪問頻率,一定時間內頻繁(超出正常使用者的極限)訪問,可以對請求方加以限制(如 IP 限制)。另外,一些介面可以加入認證,必須登入才能訪問。

快取擊穿

通常情況,我們會為快取設定一個過期時間。而如果在一個資源的快取過期以後(或者還未來得及快取),瞬間湧入大量查詢該資源的請求,那麼這些請求就都會一股腦的奔向資料庫,這時,我們的資料庫可就慘了,可能秒秒鐘掛掉。這種情況我們稱之為快取擊穿。

如何解決

要解決快取擊穿也有兩種思路:

  • 永不過期。
  • 加鎖。
  • 先看第一種,短時間內被大量訪問的通常是熱點資源,針對這類資源我們可以不設定過期時間(永不過期),當資源有變化時透過程式去更新快取。

    再來看第二種,我們可以使用加鎖的方式(一般 JVM 級別的鎖即可)來避免擊穿。當快取過期之後,進來的請求,先要獲得一把鎖(也就是去資料庫查詢的資格),然後再去查詢資料庫,最後將資料新增到快取。這樣就可以保證同一時刻(一個服務例項)只會有一個請求去查庫了,其他執行緒等快取有值以後,再去快取取。

    加鎖虛擬碼示例:

    public String getData() throws InterruptedException {
        // 從快取取值
        String result = getFromCache();
        // 取到直接返回
        if (Objects.nonNull(result)) {
            return result;
        }  
        // 嘗試獲取鎖
        if (!lock.tryLock()) {
            // 加鎖失敗則休息一會
            Thread.sleep(10);
            return getData();
        }
        // 加鎖成功則去資料庫取值
        result = getFromDB();
        // 取回後放入快取
        setFromCache();
        return result;
    }
    快取雪崩

    快取雪崩指的是,快取中大量的 key 在同一時刻集體過期,導致大量請求湧入到資料庫。

    有人把快取服務由於一些原因不可用稱為快取雪崩,我覺得這麼叫不太合適。

    你想象一下什麼是雪崩,大量的雪花集體從山上往下跳就是雪崩。那麼對應到快取的場景,我們可以把 Redis 看做是山,而 Redis 裡的 key 就是雪花。Redis 中大量的 key 同時失效,就好比是山上大量的雪花同時往下掉是一樣的。所以雪崩用來比喻大量 key 集中失效的情況明顯更貼切。而快取服務掛掉應該屬於快取服務故障,可以採取快取叢集的方式來提高可用性。

    如何解決

    要解決快取雪崩的問題,有兩種思路:

  • 分散過期時間。
  • 永不過期。
  • 分散過期時間很容易想到,既然雪崩是因為 key 集體過期導致的,那麼把它們過期的時間分散開就可以避免這種問題了。

    另一種思路,跟解決快取擊穿一樣,將快取設定為永不過期。

    永不過期的方案有一定的侷限性,要看具體的業務,不能粗暴的將所有快取都設定成不過期。

    總結

    每種技術方案都有其適用的業務場景,也都有其侷限性。沒有一個方案能夠應對所有問題,合適即是好。但從上面的方案中還是能看到一些通用的思想的,比如:儘早返回。咋理解呢?就是讓呼叫鏈儘量的短,能攔在應用服務之前的絕不放行(布隆過濾);能從快取取到的絕不再去查庫。

    原文來自:


    來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2906857/,如需轉載,請註明出處,否則將追究法律責任。

    相關文章