學習筆記 | 7大緩存經典問題

7大緩存經典問題

在這裏插入圖片描述

01 緩存失效

問題描述

  • 服務系統查數據,首先會查緩存,如果緩存數據不存在,就進一步查 DB,最後查到數據後回種到緩存並返回。緩存的性能比 DB 高 50~100 倍以上,所以我們希望數據查詢儘可能命中緩存,這樣系統負荷最小,性能最佳。
  • 緩存裏的數據存儲基本上都是以 key 爲索引進行存儲和獲取的。業務訪問時,如果大量的 key 同時過期,很多緩存數據訪問都會 miss,進而穿透到 DB,DB 的壓力就會明顯上升,由於 DB 的性能較差,只在緩存的 1%~2% 以下,這樣請求的慢查率會明顯上升。這就是緩存失效的問題。

原因分析

  • 導致緩存失效,特別是很多 key 一起失效的原因,跟我們日常寫緩存的過期時間息息相關。
  • 在寫緩存時,我們一般會根據業務的訪問特點,給每種業務數據預置一個過期時間,在寫緩存時把這個過期時間帶上,讓緩存數據在這個固定的過期時間後被淘汰。一般情況下,因爲緩存數據是逐步寫入的,所以也是逐步過期被淘汰的。但在某些場景,一大批數據會被系統主動或被動從 DB 批量加載,然後寫入緩存。這些數據寫入緩存時,由於使用相同的過期時間,在經歷這個過期時間之後,這批數據就會一起到期,從而被緩存淘汰。此時,對這批數據的所有請求,都會出現緩存失效,從而都穿透到 DB,DB 由於查詢量太大,就很容易壓力大增,請求變慢。

業務場景

  • 很多業務場景,稍不注意,就出現大量的緩存失效,進而導致系統 DB 壓力大、請求變慢的情況。比如同一批火車票、飛機票,當可以售賣時,系統會一次性加載到緩存,如果緩存寫入時,過期時間按照預先設置的過期值,那過期時間到期後,系統就會因緩存失效出現變慢的問題。類似的業務場景還有很多,比如微博業務,會有後臺離線系統,持續計算熱門微博,每當計算結束,會將這批熱門微博批量寫入對應的緩存。還比如,很多業務,在部署新 IDC 或新業務上線時,會進行緩存預熱,也會一次性加載大批熱數據。

解決方案

  • 對於批量 key 緩存失效的問題,原因既然是預置的固定過期時間,那解決方案也從這裏入手。
  • 設計緩存的過期時間時,使用公式:過期時間=baes 時間+隨機時間。即相同業務數據寫緩存時,在基礎過期時間之上,再加一個隨機的過期時間,讓數據在未來一段時間內慢慢過期,避免瞬時全部過期,對 DB 造成過大壓力,如下圖所示。

02 緩存穿透

問題描述

  • 緩存穿透是一個很有意思的問題。因爲緩存穿透發生的概率很低,所以一般很難被發現。但是,一旦你發現了,而且量還不小,你可能立即就會經歷一個忙碌的夜晚。因爲對於正常訪問,訪問的數據即便不在緩存,也可以通過 DB 加載回種到緩存。
  • 而緩存穿透,則意味着有特殊訪客在查詢一個不存在的 key,導致每次查詢都會穿透到 DB,如果這個特殊訪客再控制一批肉雞機器,持續訪問你係統裏不存在的 key,就會對 DB 產生很大的壓力,從而影響正常服務。

原因分析

  • 緩存穿透存在的原因,就是因爲我們在系統設計時,更多考慮的是正常訪問路徑,對特殊訪問路徑、異常訪問路徑考慮相對欠缺
  • 緩存訪問設計的正常路徑,是先訪問 cache,cache miss 後查 DB,DB 查詢到結果後,回種緩存返回。這對於正常的 key 訪問是沒有問題的,但是如果用戶訪問的是一個不存在的 key,查 DB 返回空(即一個 NULL),那就不會把這個空寫回cache。那以後不管查詢多少次這個不存在的 key,都會 cache miss,都會查詢 DB。整個系統就會退化成一個 “前端+DB“ 的系統,由於 DB 的吞吐只在 cache 的 1%~2% 以下,如果有特殊訪客,大量訪問這些不存在的 key,就會導致系統的性能嚴重退化,影響正常用戶的訪問。

業務場景

  • 緩存穿透的業務場景很多,比如通過不存在的 UID 訪問用戶,通過不存在的車次 ID 查看購票信息。用戶輸入錯誤,偶爾幾個這種請求問題不大,但如果是大量這種請求,就會對系統影響非常大。

解決方案

  • 第一種方案就是,查詢這些不存在的數據時,第一次查 DB,雖然沒查到結果返回 NULL,仍然記錄這個 key 到緩存,只是這個 key 對應的 value 是一個特殊設置的值。
  • 第二種方案是,構建一個 BloomFilter 緩存過濾器,記錄全量數據,這樣訪問數據時,可以直接通過 BloomFilter 判斷這個 key 是否存在,如果不存在直接返回即可,根本無需查緩存和 DB。

不過這兩種方案在設計時仍然有一些要注意的坑。

  • 對於方案一,如果特殊訪客持續訪問大量的不存在的 key,這些 key 即便只存一個簡單的默認值,也會佔用大量的緩存空間,導致正常 key 的命中率下降。所以進一步的改進措施是,對這些不存在的 key 只存較短的時間,讓它們儘快過期;或者將這些不存在的 key 存在一個獨立的公共緩存,從緩存查找時,先查正常的緩存組件,如果 miss,則查一下公共的非法 key 的緩存,如果後者命中,直接返回,否則穿透 DB,如果查出來是空,則回種到非法 key 緩存,否則回種到正常緩存。
  • 對於方案二,BloomFilter 要緩存全量的 key,這就要求全量的 key 數量不大,10億條數據以內最佳,因爲 10億 條數據大概要佔用 1.2GB 的內存。也可以用 BloomFilter 緩存非法 key,每次發現一個 key 是不存在的非法 key,就記錄到 BloomFilter 中,這種記錄方案,會導致 BloomFilter 存儲的 key 持續高速增長,爲了避免記錄 key 太多而導致誤判率增大,需要定期清零處理。

03 緩存雪崩

問題描述

緩存雪崩是指部分緩存節點不可用,導致整個緩存體系甚至甚至服務系統不可用的情況。緩存雪崩按照緩存是否 rehash(即是否漂移)分兩種情況:

  • 緩存不支持 rehash 導致的系統雪崩不可用
  • 緩存支持 rehash 導致的緩存雪崩不可用

原因分析

在上述兩種情況中,緩存不進行 rehash 時產生的雪崩,一般是由於較多緩存節點不可用,請求穿透導致 DB 也過載不可用,最終整個系統雪崩不可用的。而緩存支持 rehash 時產生的雪崩,則大多跟流量洪峯有關,流量洪峯到達,引發部分緩存節點過載 Crash,然後因 rehash 擴散到其他緩存節點,最終整個緩存體系異常。

  • 第一種情況比較容易理解,如下圖所示。緩存節點不支持 rehash,較多緩存節點不可用時,大量 Cache 訪問會失敗,根據緩存讀寫模型,這些請求會進一步訪問 DB,而且 DB 可承載的訪問量要遠比緩存小的多,請求量過大,就很容易造成 DB 過載,大量慢查詢,最終阻塞甚至 Crash,從而導致服務異常。

  • 第二種情況是怎麼回事呢?這是因爲緩存分佈設計時,很多同學會選擇一致性 Hash 分佈方式,同時在部分節點異常時,採用 rehash 策略,即把異常節點請求平均分散到其他緩存節點。在一般情況下,一致性 Hash 分佈+rehash 策略可以很好得運行,但在較大的流量洪峯到臨之時,如果大流量 key 比較集中,正好在某 1~2 個緩存節點,很容易將這些緩存節點的內存、網卡過載,緩存節點異常 Crash,然後這些異常節點下線,這些大流量 key 請求又被 rehash 到其他緩存節點,進而導致其他緩存節點也被過載 Crash,緩存異常持續擴散,最終導致整個緩存體系異常,無法對外提供服務。

業務場景

  • 緩存雪崩的業務場景並不少見,微博、Twitter 等系統在運行的最初若干年都遇到過很多次。比如,微博最初很多業務緩存採用一致性 Hash+rehash 策略,在突發洪水流量來臨時,部分緩存節點過載 Crash 甚至宕機,然後這些異常節點的請求轉到其他緩存節點,又導致其他緩存節點過載異常,最終整個緩存池過載。另外,機架斷電,導致業務緩存多個節點宕機,大量請求直接打到 DB,也導致 DB 過載而阻塞,整個系統異常。最後緩存機器覆電後,DB 重啓,數據逐步加熱後,系統才逐步恢復正常。

解決方案:預防緩存雪崩

  • 方案一,對業務 DB 的訪問增加讀寫開關,當發現 DB 請求變慢、阻塞,慢請求超過閥值時,就會關閉讀開關,部分或所有讀 DB 的請求進行 failfast 立即返回,待 DB 恢復後再打開讀開關,如下圖。
  • 方案二,對緩存增加多個副本,緩存異常或請求 miss 後,再讀取其他緩存副本,而且多個緩存副本儘量部署在不同機架,從而確保在任何情況下,緩存系統都會正常對外提供服務。

  • 方案三,對緩存體系進行實時監控,當請求訪問的慢速比超過閥值時,及時報警,通過機器替換、服務替換進行及時恢復;也可以通過各種自動故障轉移策略,自動關閉異常接口、停止邊緣服務、停止部分非核心功能措施,確保在極端場景下,核心功能的正常運行。

實際上,微博平臺系統,這三種方案都採用了,通過三管齊下,規避緩存雪崩的發生。

04 緩存數據不一致

  • 同一份數據,可能會同時存在 DB 和緩存之中。那就有可能發生,DB 和緩存的數據不一致。
  • 如果緩存有多個副本,多個緩存副本里的數據也可能會發生不一致現象。

原因分析

  • 不一致的問題大多跟緩存更新異常有關。比如更新 DB 後,寫緩存失敗,從而導致緩存中存的是老數據。
  • 另外,如果系統採用一致性 Hash 分佈,同時採用 rehash 自動漂移策略,在節點多次上下線之後,也會產生髒數據。
  • 緩存有多個副本時,更新某個副本失敗,也會導致這個副本的數據是老數據。

業務場景

  • 導致數據不一致的場景也不少。如下圖所示,在緩存機器的帶寬被打滿,或者機房網絡出現波動時,緩存更新失敗,新數據沒有寫入緩存,就會導致緩存和 DB 的數據不一致。緩存 rehash 時,某個緩存機器反覆異常,多次上下線,更新請求多次 rehash。這樣,一份數據存在多個節點,且每次 rehash 只更新某個節點,導致一些緩存節點產生髒數據。

解決方案:要儘量保證數據的一致性。

  • 第一個方案,cache 更新失敗後,可以進行重試,如果重試失敗,則將失敗的 key 寫入隊列機服務,待緩存訪問恢復後,將這些 key 從緩存刪除。 這些 key 在再次被查詢時,重新從 DB 加載,從而保證數據的一致性。
  • 第二個方案,緩存時間適當調短,讓緩存數據及早過期後,然後從 DB 重新加載,確保數據的最終一致性。
  • 第三個方案,不採用 rehash 漂移策略,而採用緩存分層策略,儘量避免髒數據產生。

05 數據併發競爭

問題描述

互聯網系統,線上流量較大,緩存訪問中很容易出現數據併發競爭的現象。數據併發競爭,是指在高併發訪問場景,一旦緩存訪問沒有找到數據,大量請求就會併發查詢 DB,導致 DB 壓力大增的現象。

  • 數據併發競爭,主要是由於多個進程/線程中,有大量併發請求獲取相同的數據,而這個數據 key 因爲正好過期、被剔除等各種原因在緩存中不存在,這些進程/線程之間沒有任何協調,然後一起併發查詢 DB,請求那個相同的 key,最終導致 DB 壓力大增,如下圖。

業務場景

  • 數據併發競爭在大流量系統也比較常見,比如車票系統,如果某個火車車次緩存信息過期,但仍然有大量用戶在查詢該車次信息。又比如微博系統中,如果某條微博正好被緩存淘汰,但這條微博仍然有大量的轉發、評論、贊。上述情況都會造成該車次信息、該條微博存在併發競爭讀取的問題。

解決方案

要解決併發競爭,有 2 種方案。

  • 方案一是使用全局鎖。如下圖所示,即當緩存請求 miss 後,先嚐試加全局鎖,只有加全局鎖成功的線程,纔可以到 DB 去加載數據。其他進程/線程在讀取緩存數據 miss 時,如果發現這個 key 有全局鎖,就進行等待,待之前的線程將數據從 DB 回種到緩存後,再從緩存獲取。
  • 方案二是,對緩存數據保持多個備份,即便其中一個備份中的數據過期或被剔除了,還可以訪問其他備份,從而減少數據併發競爭的情況,如下圖。

06 Hot key

問題描述

  • 對於大多數互聯網系統,數據是分冷熱的。
  • 比如最近的新聞、新發表的微博被訪問的頻率最高,而比較久遠的之前的新聞、微博被訪問的頻率就會小很多。而在突發事件發生時,大量用戶同時去訪問這個突發熱點信息,訪問這個 Hot key,這個突發熱點信息所在的緩存節點就很容易出現過載和卡頓現象,甚至會被 Crash。

原因分析

  • Hot key 引發緩存系統異常,主要是因爲突發熱門事件發生時,超大量的請求訪問熱點事件對應的 key,比如微博中數十萬、數百萬的用戶同時去喫一個新瓜。數十萬的訪問請求同一個 key,流量集中打在一個緩存節點機器,這個緩存機器很容易被打到物理網卡、帶寬、CPU 的極限,從而導致緩存訪問變慢、卡頓。

業務場景

  • 引發 Hot key 的業務場景很多,比如明星結婚、離婚、出軌這種特殊突發事件,比如奧運、春節這些重大活動或節日,還比如秒殺、雙12、618 等線上促銷活動,都很容易出現 Hot key 的情況。

解決方案

要解決這種極熱 key 的問題,首先要找出這些 Hot key 來。對於重要節假日、線上促銷活動、集中推送這些提前已知的事情,可以提前評估出可能的熱 key 來。而對於突發事件,無法提前評估,可以通過 Spark,對應流任務進行實時分析,及時發現新發布的熱點 key。而對於之前已發出的事情,逐步發酵成爲熱 key 的,則可以通過 Hadoop 對批處理任務離線計算,找出最近歷史數據中的高頻熱 key。

  • 找到熱 key 後,就有很多解決辦法了。首先可以將這些熱 key 進行分散處理,比如一個熱 key 名字叫 hotkey,可以被分散爲 hotkey#1、hotkey#2、hotkey#3,……hotkey#n,這 n 個 key 分散存在多個緩存節點,然後 client 端請求時,隨機訪問其中某個後綴的 hotkey,這樣就可以把熱 key 的請求打散,避免一個緩存節點過載,如下圖所示。
    在這裏插入圖片描述
  • 其次,也可以 key 的名字不變,對緩存提前進行多副本+多級結合的緩存架構設計。
  • 再次,如果熱 key 較多,還可以通過監控體系對緩存的 SLA 實時監控,通過快速擴容來減少熱 key 的衝擊。
  • 最後,業務端還可以使用本地緩存,將這些熱 key 記錄在本地緩存,來減少對遠程緩存的衝擊。

07 Big key

問題描述

  • 大 key,是指在緩存訪問時,部分 Key 的 Value 過大,讀寫、加載易超時的現象。
原因分析
  • 造成這些大 key 慢查詢的原因很多。如果這些大 key 佔總體數據的比例很小,存 Mc,對應的 slab 較少,導致很容易被頻繁剔除,DB 反覆加載,從而導致查詢較慢。如果業務中這種大 key 很多,而這種 key 被大量訪問,緩存組件的網卡、帶寬很容易被打滿,也會導致較多的大 key 慢查詢。另外,如果大 key 緩存的字段較多,每個字段的變更都會引發對這個緩存數據的變更,同時這些 key 也會被頻繁地讀取,讀寫相互影響,也會導致慢查現象。最後,大 key 一旦被緩存淘汰,DB 加載可能需要花費很多時間,這也會導致大 key 查詢慢的問題。

業務場景

  • 大 key 的業務場景也比較常見。比如互聯網系統中需要保存用戶最新 1萬 個粉絲的業務,比如一個用戶個人信息緩存,包括基本資料、關係圖譜計數、發 feed 統計等。微博的 feed 內容緩存也很容易出現,一般用戶微博在 140 字以內,但很多用戶也會發表 1千 字甚至更長的微博內容,這些長微博也就成了大 key,如下圖。

解決方案

對於大 key,給出 3 種解決方案。

  • 第一種方案,如果數據存在 Mc 中,可以設計一個緩存閥值,當 value 的長度超過閥值,則對內容啓用壓縮,讓 KV 儘量保持小的 size,其次評估大 key 所佔的比例,在 Mc 啓動之初,就立即預寫足夠數據的大 key,讓 Mc 預先分配足夠多的 trunk size 較大的 slab。確保後面系統運行時,大 key 有足夠的空間來進行緩存。
  • 第二種方案,如果數據存在 Redis 中,比如業務數據存 set 格式,大 key 對應的 set 結構有幾千幾萬個元素,這種寫入 Redis 時會消耗很長的時間,導致 Redis 卡頓。此時,可以擴展新的數據結構,同時讓 client 在這些大 key 寫緩存之前,進行序列化構建,然後通過 restore 一次性寫入,如下圖所示。
  • 第三種方案時,如下圖所示,將大 key 分拆爲多個 key,儘量減少大 key 的存在。同時由於大 key 一旦穿透到 DB,加載耗時很大,所以可以對這些大 key 進行特殊照顧,比如設置較長的過期時間,比如緩存內部在淘汰 key 時,同等條件下,儘量不淘汰這些大 key。
    在這裏插入圖片描述
  • 我們要認識到,對於互聯網系統,由於實際業務場景複雜,數據量、訪問量巨大,需要提前規避緩存使用中的各種坑。
  • 你可以通過提前熟悉 Cache 的經典問題,提前構建防禦措施, 避免大量 key 同時失效,避免不存在 key 訪問的穿透,減少大 key、熱 key 的緩存失效,對熱 key 進行分流。
  • 你可以採取一系列措施,讓訪問儘量命中緩存,同時保持數據的一致性。
  • 另外,你還可以結合業務模型,提前規劃 cache 系統的 SLA,如 QPS、響應分佈、平均耗時等,實施監控,以方便運維及時應對。
  • 在遇到部分節點異常,或者遇到突發流量、極端事件時,也能通過分池分層策略、key 分拆等策略,避免故障發生。
  • 最終,你能在各種複雜場景下,面對高併發、海量訪問,面對突發事件和洪峯流量,面對各種網絡或機器硬件故障,都能保持服務的高性能和高可用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章