緩存使用場景
緩存是提升應用程序性能的最多見的一種技術,常適用於讀多寫少的場景。按照架構層次能夠分爲:客戶端緩存、頁面緩存、應用緩存、持久層緩存。
其中應用緩存設計最爲複雜,本文主要討論應用緩存的設計。redis
更新or淘汰
緩存的存儲通常爲鍵值對的方式,存儲的值可能爲對象或集合或單值,好比用戶信息、購物車列表、閱讀計數等。而緩存發生變化時的更新則存在兩種策略:更新or淘汰。通常來講淘汰最爲簡單。
淘汰數據能夠簡單的將緩存刪除,更新數據則須要將新的值放入緩存中;淘汰數據會致使查找緩存時的一次miss,而更新緩存則不存在此問題,特別時某些複雜場景下淘汰緩存可能會致使緩存穿透。
若是更新緩存不須要增長額外的複雜計算或者分支流程推薦使用更新緩存的策略。另外若是對主線流程的響應速度要求比較高能夠採用異步方式更新緩存。算法
緩存對象or單值數據
通常傾向於緩存單值數據而非對象,固然不包含其它業務對象的數據可視爲單值數據(能夠存儲爲string)。例如redis,支持List、Hash、Set(ZSet)和String,對於其中的集合對象能夠根據不一樣的業務場景選用。
存儲一個複雜對象須要將對象的屬性拆分紅不一樣的key-value來存儲,好比:數據庫
Book{ string id; int buyCount; string name; }。
能夠拆分紅:
Book.ID.buyCount = buyCount、Book.ID.name = name。
這樣的存儲會加大緩存對象的讀寫開銷。若是buyCount不須要單獨讀寫則可視爲單值數據,能夠以json的方式存取。具體如何選擇須要視不一樣場景而定。json
緩存穿透與失效
緩存設計不得不考慮的另外一個問題是緩存穿透與失效時的雪崩效應。緩存穿透是指查詢一個必定不存在的數據,因爲緩存miss時會從持久存儲(DB)中查詢數據,而且出於容錯考慮查不到數據的時候不會寫入緩存,這將致使每次查詢這個數據時都要到存儲層去查詢,而失去了緩存的意義。
緩存穿透:有不少種方法能夠有效地解決緩存穿透問題,最多見的則是採用布隆過濾器,將全部可能存在的數據hash到一個足夠大的bitmap中,一個必定不存在的數據會被 這個bitmap攔截掉,從而避免了對持久存儲(DB)的查詢壓力,這種方法的缺陷是hash及查詢bitmap的性能開銷以及bitmap的存儲開銷,在極端狀況下會致使得不償失。另一種方法是將n巨ull最爲這個數據的值放入緩存並設定緩存的失效時間,這種方法的缺陷是低頻的緩存穿透以及失效時間可能帶來的性能開銷(特別是在分佈式的緩存系統中)。
緩存失效:這個問題目前並無很完美的解決方案。大多數系統設計者考慮用加鎖或者隊列的方式保證緩存的單線 程(進程)寫,從而避免失效時大量的併發請求落到持久存儲層(DB)上。固然併發量不是特別大的系統不須要處理。緩存
觸發時機
程序中咱們能夠在數據持久之間更新緩存或者在數據持久以後更新緩存。
在數據持久以前更新緩存會存在這樣的問題:緩存更新成功,數據持久失敗。這種狀況下須要額外的回饋機制處理緩存的回滾,複雜且不夠高效。
而在數據持久以後更新緩存則可能會存在數據持久成功,緩存更新失敗的狀況,這種狀況下須要再次更新緩存或者直接失效緩存,一樣須要額外的回饋機制處理緩存更新失敗的狀況。
如何選擇須要考慮的是對主線業務的影響大小以及緩存髒數據對系統的影響大小,通常來講緩存髒數據並不會形成系統性的影響。架構
分佈式緩存
緩存系統須要考慮幾個問題:併發
而由此催發出來的分佈式緩存系統則須要額外考慮這幾個問題異步
成熟的分佈式緩存系統有Redis、Memcached、Ehcache、阿里雲的OSC等等,各個系統的使用場景和實現方式各不相同這裏再也不深究。分佈式
系統自己的管理問題,包括了存儲空間的分配、擴展、回收機制性能
分佈式節點管理和路由算法
緩存鍵值的管理
緩存自己的水平線性擴展問題
緩存大併發下的自己的性能問題
緩存的單點故障問題(多副本和副本一致性)
緩存+數據庫
一般咱們使用緩存是配合數據庫特別是關係型數據庫,而數據庫則存在主從、讀寫分離等提升性能和可用性的方案。在這樣的狀況下,設計緩存系統須要特別注意數據的分發傳遞速度以及數據庫單點故障可能致使的緩存更新失敗。沒有特殊處理的話可能會引起數據庫數據與緩存數據的不一致。
服務化 緩存系統須要對外提供統一透明的服務API,你懂的,不解釋!