快取系統中面臨的雪崩/穿透/一致性問題

架構師springboot發表於2019-02-13

電腦科學中有兩件難事:快取失效和命名

– Phil Karlton

From Martin Fowler : TwoHardThings

快取系統一定程度上極大提升系統併發能力,但同樣也增加額外技術考慮因素,下面針對快取系統設計與使用中面臨的常見問題展開。

  • 快取應用的典型場景
  • 快取雪崩
  • 快取穿透
  • 快取更新與資料一致性

快取應用的典型場景

請求->快取->命中快取則返回資料->無快取則讀取原始資料來源

快取定位 :前置資料載入,避免資料回源,提供高效能、高併發的資料讀取能力;只有未命中快取時才進行資料回源,極大減輕原始資料讀取的壓力

快取分類 :按快取系統所處位置不同,分為本地快取、分散式快取

  • 本地快取:記憶體級快取、檔案級快取,記憶體級快取優勢在於本地記憶體I/O、高效能(單次記憶體定址100ns),缺點在於空間有限,無法多端資料同步,此類方案有PHP的Opcache/Yac, Java中Encache/GuavaCache/SpringCache等;檔案級快取依賴磁碟I/O實現快取作用,受機械磁碟尋道效能限制(單次磁碟讀取時間10ms左右),或考慮固態硬碟/Raid優化方案,較少使用
  • 分散式快取:Memcached、Redis等,分散式系統解決快取容量問題,具備持續擴容能力,但不可避免一次網路I/O請求

本文主要討論 分散式快取 系統設計與使用中面臨的問題。

快取雪崩

定義: 快取雪崩是指快取系統失效,導致大量請求同時進行資料回源,導致資料來源壓力驟增而崩潰 。兩種情況會導致此問題:1、多個快取資料同時失效;2、快取系統崩潰

快取同時失效

  • 在大量快取同時失效的情況下,請求回源,導致資料來源請求暴增而崩潰,系統全域性不可用
  • 快取時間設定原則:根據 快取資料訪問規律和快取資料不一致的敏感性 要求來選擇快取時間
  • 快取資料訪問規律:如不同快取資料訪問無規律或相對離散,則不會存在這些快取資料同時失效的情況;如 快取資料為批量寫入 (定時任務預熱),應考慮將 快取時間離散化 ,避免同時失效的情況下大量回源請求
  • 快取資料不一致的敏感性:不同應用場景下對快取資料的一致性要求不同,快取時間的設定視情況而定
  • 這裡也涉及到快取更新策略問題,錯誤的更新策略可能會先刪除快取,再設定快取,此時間差範圍內的請求會進行回源,會導致此問題

如何避免應考慮: 快取失效時間離散化

快取系統故障

快取系統整體故障,則整個快取系統不可用,大量回源請求,且由於快取系統故障無法回寫快取,導致無法快速恢復。

一句老話:為解決一個問題,引入新的解決方案,同時也必然引入新的問題。

這也是快取系統的引入,在解決高效能、高併發的同時,引入了新的故障點。

考慮此問題,應從事前、事故中、事後不同階段考慮:

  • 事前:增加快取系統 高可用方案設計 ,避免出現系統性故障
  • 事故中:
    熔斷限流機制
    複製程式碼
  • 事後:快取 資料持久化 ,在故障後 快速恢復 快取系統

快取穿透

定義: 快取穿透是指訪問不存在資料,從而繞過快取,直取資料來源(大量資料來源讀取操作)

解決快取穿透的思路:

  • 不存在資源訪問時,在快取系統設定空值來攔截
    • 優點:實現簡單
    • 問題:大量非法請求時,快取系統被填充大量非法值
  • 根據資源設定攔截機制(布隆過濾器bloomfilter或壓縮filter過濾有效資源,如有效使用者id等;也可以全域性儲存有效資源摘要,專用過濾、防穿透)
    • 優點:快取系統空間利用較好
    • 問題:過濾器實現機制和資料一致性要求

快取更新與資料一致性

快取系統資料的更新策略是需要專門開題來說的,建議閱讀 左耳朵耗子:快取更新的套路 系統瞭解,這裡只根據實際經驗給出在不同一致性要求下的建議。

一種常見快取更新策略(此方案有問題):

  • 讀操作:命中快取則返回,無快取則取回源資料,寫快取
  • 寫操作:先刪除快取,再更新資料來源

問題場景:讀寫併發的場景下先刪快取操作可能導致髒資料入快取

  • 寫操作:刪除快取
  • 讀操作:無快取則取回源資料(舊資料),回寫快取(此時快取中為舊資料)
  • 寫操作:更新資料來源
  • 此時快取資料不一致:快取中為舊資料,資料來源為新資料,出現快取舊資料問題

幾種更新快取的策略:

  • Cache Aside Pattern:快取失效時回源取資料,更新快取;命中快取時,返回快取資料;先資料來源更新後,再失效快取(由等待下次讀取來回寫快取)
    • 優勢:無快取舊資料問題、快取系統維護簡單、Facebook推薦方案
    • 問題:無法絕對杜絕併發讀寫問題
      • 快取過期的背景下,讀操作回源取資料(此時為舊資料)
      • 寫操作:更新資料來源,失效快取
      • 讀操作:將回源資料(舊資料)寫快取,出現快取資料不一致問題
      • 這種問題出現概率極低,幾點要求:快取已過期、併發讀寫、讀資料比寫資料快、但讀操作更新快取比寫操作失效快取慢(也就是說寫操作的行為需完全發生在讀操作兩步之間),一般而言讀操作(讀庫+更新快取)時長要小於寫操作(更新資料來源+失效快取),所以認為這種併發問題概率較低
      • 是否可進一步解決此問題:增加鎖機制,解決併發問題
  • Read Through Pattern:更新資料來源由快取系統操作
    讀取資料
    複製程式碼
  • Write Through Pattern:更新資料來源由快取系統操作
    寫資料
    Read Through
    複製程式碼
  • Write Behind Caching Pattern:又稱 Write Back
    • 一句話總結:更新資料時,只更新快取,不更新資料來源(快取 非同步批量 更新資料來源)
    • 優勢:
      • 更新快取為記憶體操作,讀寫I/O非常高
      • 非同步批量更新資料來源,合併多個操作
    • 問題:
      • 快取不滿足強一致性要求
      • 強一致性和高效能的衝突高可用和高效能的衝突 終究會使Trade-Off
      • 實現複雜,需跟蹤哪些Cache更新,成本較高

總體來說,不同方案在不同場景下是有各自優劣的,技術選型、架構設計應根據實際場景取捨,並對選擇方案的利弊有足夠且深入理解。

一般而言,推薦 Cache Aside Pattern 方案,容忍較小概率的不一致(同時也可以增加鎖機制解決此低概率併發問題),簡化快取系統複雜度。

感興趣的可以自己來我的Java架構群,可以獲取免費的學習資料,群號:855801563對Java技術,架構技術感興趣的同學,歡迎加群,一起學習,相互討論。


相關文章