# 一看就懂快取三大問題的因果關係與解決辦法 (超重點整理) ###### tags: `Redis` [TOC] ## 一看就懂快取三大問題的因果關係與解決辦法 (超重點整理) ### 先知道快取在做些甚麼 快取資料存取的地方與資料庫不同,快取的資料存在於記憶體中,資料庫存在硬碟(HDD、SSD)當中,所以在存取速度的比較上快取佔據了較大的優勢。 資料響應速度也是對於一個系統好壞評估的一大重點,所以現今的大型系統幾乎都會使用快取機制來作為資料讀取的前哨站。  如上圖所示,上圖為一簡單的架構圖,當Request進入服務時服務需要到資料存取層拿到資料後才能響應給客戶端,一般的架構設計會先至快取要資料,看快取是否有我們的目標資料,如果沒有再去資料庫拿。 這種架構會帶來一些好處,例如: 1. 加速使用者得到響應的速度, 2. 降低資料庫的負擔 快取作為提升系統服務速度的解決方案,但畢竟快取也是一種資源,運行途中可能會出現故障、拿不到預設能拿到的資料等問題,上述問題則會衍生出後續所講述的快取三大問題 ### 快取雪崩 (Cache Avalanche) #### 發生起因與反應過程 快取雪崩 (Cache Avalanche),顧名思義就是由快取元件出現某些問題,引發一連串的事故產生,使得系統最終出現狀況。 如下圖,一般系統設計上,流量進入系統時會先到快取拿資料以完成快速響應;當系統接收高併發請求且剛好快取當機(故障)或大量快取過期則會引發此問題,在快取失效的狀況下,request的第一反應會到資料庫去拿資料(因為在快取拿不到想要的資料),而資料庫也會有流量負載上限,當大量請求灌進資料庫時就會換資料庫無法負荷而關機(故障),此時公司的DBA就會去重啟DB,但流量問題依舊存在所以資料庫重啟後又會被打掛,問題一直循環。  #### 解決辦法 最直觀的辦法就是針對負責資料處理的機器與流量進行防範。 ##### 機器面向 * 在機器上為資料存取層(快取或DB,這邊以快取為例)增加主從式架構,當master快取故障時slave快取可快速補上master的工作,當然這個解決方案也會有弊端,因為如何判斷master的流量承受狀況需要一台監控機器,如果監控機器也發生故障的話,主從式快取架構等於失效。 * 若系統架構為分散式也可以在本地架一個快取服務,這樣服務就能直接在本地快取拿資料,就不用跑到外部快取集群拿資料了, ##### 資料存取面向 此部分可針對兩個解法著手 1. 制定快取過期時間策略 對於各個Cache的過期時間進行完善的規劃,也就是不要讓Cache在同個時間點過期,至於多久要過期,就取決於該資料實際上需要的更新頻率,或是設定成永不過期。 2. 利用互斥鎖 這種狀況基本會出現在資料從資料庫寫回快取的場景,在快取層實作互斥鎖,基於其特性,同一個時間內只能有一筆請求向資料庫拿資料,拿完後寫回快取再讓其他請求從快取拿資料,如此一來就能降低請求塞爆資料庫的狀況。 ##### 流量 對於系統管理者而言,盡量不要對硬體設備或任一服務抱有太大的信心,畢竟每個服務都會有故障的機率,因此可從最根本的流量問題進行防範。 因為無論是快取層還是資料庫都會有出錯的機率,可以將它們視為資源。若一個流量較大的系統,有一個資源不可用,可能會造成所有請求在取得這個資源時異常,造成整個系統不可用,因此可加入限流(流量限制)、服務降級的操作來減少當下流量的湧入,降低系統故障的機率。 #### 解決方案架構  在這個解決方案下,在系統本地端建立快取(可視為一個master),當快取沒資料時再去外部的分散式快取拿,最倒楣的狀況就是回DB撈資料,且DB前面還有一個限流器所以可以保證資料庫絕對不會死,客戶端頂多重新整理幾次就可以繼續使用了。 ### 快取穿透 (Cache Penetration) #### 發生起因與反應過程 請求的資料從頭到尾都不存在快取跟資料庫中。 因此請求穿過快取直接打在資料庫上,但資料庫也找不到對應的資料,導致所有類似的請求不斷地打在資料庫上,當流量一大,又會再次造成資料庫崩潰。 舉例常見的攻擊 * 有一組API為 api/v1/product/{$id} id為數字,我們希望這個訪問所帶的id為正整數且資料庫存在這筆資料。 有些惡意訪問則會帶入負數(-999)或是資料庫中根本不存在的id進行"大量請求",又因上述提及的架構,導致快取沒資料後大量請求打進DB,然後DB又作了一堆無意義的搜尋,一來一回資料庫可能就會死掉,上述這種行為就被稱為快取穿透,甚至再嚴重一點可能會造成雪崩。 #### 解決辦法 可以在快取層之前加上一些請求限制或驗證 ##### 快取層前的解決方案 ###### 使用布隆過濾器 布隆過濾器主要是幫我們在快取層之前過濾掉一些無效的請求,以避免過多的無效請求直接去access資料庫。 其原理就是對一個bitmap array進行index寫入,初始的index皆為0,當資料進入時會透過bitmap array的長度(N)與M個的hash function進行反應,最終得出M個數字(即為index),最終再將bitmap array對應的index改為1。 布隆過濾器一開始的資料是空的,所以需要跟資料層進行交互,把常用的資料用上述方式寫入到布隆過濾器中,讓其記得哪些index配起來是有資料的。 後續資料請求進來時即可把資料直接丟到布隆過濾器中運算,其將自動判斷資料是否在快取或資料庫中,但布隆過濾器也會有誤判的時候, 例如某組資料算出來的index跟資料庫或快取中資料的index一致,但他們壓根不一樣,這時只能透過拉長bitmap array的長度去降低誤判率了。 但是布隆過濾器提供的機制則會比較容易判斷不存在於資料層的資料 => 他說不存在那資料一定不存在,他說存在那資料可能存在 ###### 快取空值 這種方式就相對土炮一些了,不審查任何請求,就讓請求進到DB去查資料,如果資料不存在就將這筆資料在快取中紀錄為空值,如此一來下次被訪問時就知道資料不存在了。 但這樣會引發幾個問題 1. 快取空間消耗過快,在大量惡意請求下,快取一定不可能存那麼多資料,並且還有原有資料需要使用,這對於空間處理而言是不友善的 2. 一定要搭配互斥鎖,限定同一時間僅有一個請求訪問DB,否則會有大量請求塞進DB,進一步造成雪崩 #### 解決方案架構  我的實作方式會使用限流+布隆過濾器進行,透過限流管制流量(這邊依照系統高峰與離峰的值去作調整),再用布隆過濾器進一步的篩掉請求值,降低流量爆炸以及非法訪問的問題,進而避免快取擊穿 ### 快取擊穿 (Hotspot Invalid) #### 發生起因與反應過程 快取擊穿的狀況和雪崩有點類似,雪崩是在大量快取同時過期導致高流量請求打入資料庫;快取擊穿則是某個熱門的快取過期,此時又有大量請求該快取湧入,而這些流量就會又直接打在資料庫上,可能造成和雪崩一樣的結果。 快取擊穿其實就是快取雪崩的其中一個問題子集,他們最終所導致的結果是差不多的。 #### 解決辦法 問題跟雪崩類似,解法也差不多 主要是熱門快取失效,所以將這個快取寫回來基本上就可以解決了 * 互斥鎖,保證同一時間只有一個request更新快取,未能獲取互斥鎖的請求需等待鎖釋放後再重新讀取快取,否則就返回空值或者默認值,這種做法較為保險,避免快取過期時流量不會全部砸進資料庫裏頭。 * 不給熱點資料設定過期時間,這種作法通常需要第三方監控來執行,若熱門快取為產品列表之類的資料,如果對其在資料庫中進行了修改,則快取中的資料未與資料庫同步則會出現誤差,若使用在關於庫存的資料當中造成的後果會變得嚴重。通常會建立一個後台監控並對熱門資料設定過期時間,由後台去監控資料並更新快取,或在熱點資料準備好要過期之前,提前更新快取以及重新設定過期時間 #### 解決方案架構  因快取擊穿本身就可被視為快取雪崩的子集,所以在架構調整上與雪崩差不多,只加上一個後台服務來監控快取資料
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up