計算機科學中有兩件難事:緩存失效和命名緩存
– Phil Karlton網絡
From Martin Fowler : TwoHardThings架構
緩存系統必定程度上極大提高系統併發能力,但一樣也增長額外技術考慮因素,下面針對緩存系統設計與使用中面臨的常見問題展開。併發
- 緩存應用的典型場景
- 緩存雪崩
- 緩存穿透
- 緩存更新與數據一致性
緩存應用的典型場景
![](http://static.javashuo.com/static/loading.gif)
請求->緩存->命中緩存則返回數據->無緩存則讀取原始數據源異步
緩存定位
:前置數據加載,避免數據回源,提供高性能、高併發的數據讀取能力;只有未命中緩存時才進行數據回源,極大減輕原始數據讀取的壓力分佈式
緩存分類
:按緩存系統所處位置不一樣,分爲本地緩存、分佈式緩存ide
- 本地緩存:內存級緩存、文件級緩存,內存級緩存優點在於本地內存I/O、高性能(單次內存尋址100ns),缺點在於空間有限,沒法多端數據同步,此類方案有PHP的Opcache/Yac, Java中Encache/GuavaCache/SpringCache等;文件級緩存依賴磁盤I/O實現緩存做用,受機械磁盤尋道性能限制(單次磁盤讀取時間10ms左右),或考慮固態硬盤/Raid優化方案,較少使用
- 分佈式緩存:Memcached、Redis等,分佈式系統解決緩存容量問題,具有持續擴容能力,但不可避免一次網絡I/O請求
本文主要討論 分佈式緩存
系統設計與使用中面臨的問題。高併發
緩存雪崩
定義: 緩存雪崩是指緩存系統失效,致使大量請求同時進行數據回源,致使數據源壓力驟增而崩潰 。兩種狀況會致使此問題:一、多個緩存數據同時失效;二、緩存系統崩潰性能
緩存同時失效
- 在大量緩存同時失效的狀況下,請求回源,致使數據源請求暴增而崩潰,系統全局不可用
- 緩存時間設置原則:根據 緩存數據訪問規律和緩存數據不一致的敏感性 要求來選擇緩存時間
- 緩存數據訪問規律:如不一樣緩存數據訪問無規律或相對離散,則不會存在這些緩存數據同時失效的狀況;如 緩存數據爲批量寫入 (定時任務預熱),應考慮將 緩存時間離散化 ,避免同時失效的狀況下大量回源請求
- 緩存數據不一致的敏感性:不一樣應用場景下對緩存數據的一致性要求不一樣,緩存時間的設置視狀況而定
- 這裏也涉及到緩存更新策略問題,錯誤的更新策略可能會先刪除緩存,再設置緩存,此時間差範圍內的請求會進行回源,會致使此問題
如何避免應考慮: 緩存失效時間離散化
學習
緩存系統故障
緩存系統總體故障,則整個緩存系統不可用,大量回源請求,且因爲緩存系統故障沒法回寫緩存,致使沒法快速恢復。
一句老話:爲解決一個問題,引入新的解決方案,同時也必然引入新的問題。
這也是緩存系統的引入,在解決高性能、高併發的同時,引入了新的故障點。
考慮此問題,應從事前、事故中、過後不一樣階段考慮:
- 事前:增長緩存系統 高可用方案設計 ,避免出現系統性故障
- 事故中:
熔斷限流機制
- 過後:緩存 數據持久化 ,在故障後 快速恢復 緩存系統
緩存穿透
定義: 緩存穿透是指訪問不存在數據,從而繞過緩存,直取數據源(大量數據源讀取操做)
解決緩存穿透的思路:
- 不存在資源訪問時,在緩存系統設置空值來攔截
![](http://static.javashuo.com/static/loading.gif)
- 優勢:實現簡單
- 問題:大量非法請求時,緩存系統被填充大量非法值
- 根據資源設置攔截機制(布隆過濾器bloomfilter或壓縮filter過濾有效資源,若有效用戶id等;也能夠全局保存有效資源摘要,專用過濾、防穿透)
![](http://static.javashuo.com/static/loading.gif)
- 優勢:緩存系統空間利用較好
- 問題:過濾器實現機制和數據一致性要求
緩存更新與數據一致性
緩存系統數據的更新策略是須要專門開題來講的,建議閱讀 左耳朵耗子:緩存更新的套路 系統瞭解,這裏只根據實際經驗給出在不一樣一致性要求下的建議。
一種常見緩存更新策略(此方案有問題):
- 讀操做:命中緩存則返回,無緩存則取回源數據,寫緩存
- 寫操做:先刪除緩存,再更新數據源
問題場景:讀寫併發的場景下先刪緩存操做可能致使髒數據入緩存
- 寫操做:刪除緩存
- 讀操做:無緩存則取回源數據(舊數據),回寫緩存(此時緩存中爲舊數據)
- 寫操做:更新數據源
- 此時緩存數據不一致:緩存中爲舊數據,數據源爲新數據,出現緩存舊數據問題
幾種更新緩存的策略:
- 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技術,架構技術感興趣的同窗,歡迎加羣,一塊兒學習,相互討論。