前言
隨著系統(tǒng)的并發(fā)量增加亮蒋,數據庫的并發(fā)讀寫最終將成為整個提供的瓶頸献幔,甚至壓垮整個數據庫,導致系統(tǒng)卡死等嚴重問題虹蓄。通過緩存是緩解數據庫壓力的重要手段犀呼,通過緩存把絕大多數請求在讀寫數據庫前攔截掉,大大降低數據庫壓力薇组。
緩存設計思想及方案
緩存設計最核心的原則就是讓數據離用戶更近外臂。優(yōu)秀的緩存設計直接影響到系統(tǒng)的高并發(fā)性能和響應速度,甚至影響客戶體驗律胀。
緩存有三個作用范圍:
- 事務宋光、
- 應用、
- 集群
緩存設計方案
緩存按照存放位置不同可以分為客戶端緩存和服務端緩存炭菌。
客戶端緩存:
- 瀏覽器緩存
- 網關及代理服務器緩存:
服務器緩存:
- 本地緩存:
- 分布式緩存:
- 數據庫緩存:
一個好的緩存設計方案需要綜合考慮緩存的存儲罪佳、使用時機、優(yōu)缺點黑低、以及分布式高并發(fā)下緩存一致性赘艳、緩存命中、緩存擊穿/雪崩等問題克握。
緩存詳細設計方案
客戶端緩存
客戶端緩存通常指web前端緩存蕾管,可以分為:瀏覽器緩存和代理服務器緩存。是目前網站前端加速的主要方式菩暗,其實現基本方式是:將制定的網站資源(整體頁面娇掏、靜態(tài)文件(js、css勋眯、圖片)等)周期性緩存起來婴梧,緩存時間可以從幾秒到幾天不等下梢,極大減少了網站應用服務器和數據庫負荷。
瀏覽器緩存
瀏覽器緩存是最靠近客戶的緩存機制塞蹭,當開啟瀏覽器緩存后孽江,客戶訪問同一個頁面將不從服務器下載頁面,也是從瀏覽器本地緩存目錄讀取頁面番电,然后在瀏覽器中展示岗屏。對于不常變化資源可以使用強制緩存策略,而精彩變動資源可以禁止瀏覽器緩存漱办。瀏覽器緩存更新問題解決:可以在資源的引用地址(路徑)后面增加hash这刷、版本號等動態(tài)字符,從而達到更新資源引用URL目的娩井,讓之前的緩存強制失效(PS:其實并未立即失效暇屋,而是不在使用)。
網關或代理服務器緩存
將網頁緩存到代理服務器上洞辣,多個用戶訪問同一個頁面時咐刨,將直接從代理服務器把頁面?zhèn)魉徒o用戶。常見實現如扬霜,Ngnix反向代理緩存定鸟。使用反向代理服務器緩存實現簡單,通常通過簡單配置便可以實現著瓶,而不需要外增加代碼開發(fā)联予。反向代理服務器緩存適合實時性要求不高或者經常不變的頁面,如果門戶首頁材原、商品詳情等頁面沸久。
服務端緩存
在服務端編程中,緩存主要是將數據庫中數據加載到內存中华糖,之后對該數據的讀寫都是在內存中完成麦向,減少對數據庫的訪問瘟裸,是解決高并發(fā)場景數據庫并發(fā)讀寫瓶頸的主要手段之一客叉。同時基于內存的讀寫處理速度高于磁盤I/O,緩存也是提高服務響應速度和性能重要手段话告。
根據緩存是否與應用在同一進程兼搏,可以將緩存分為本地緩存和分布式緩存:
- 本地緩存:應用同一進程內存空間緩存數據,數據讀寫都在同一進程沙郭。
- 分布式緩存:獨立部署的進程佛呻,通常與應用進行部署在不同機器,緩存的讀寫需要通過網絡來完成數據的傳輸病线。
本地緩存
本地緩存優(yōu)缺點
- 訪問速度快吓著,但無法緩存大數據:本地緩存不需要跨網絡傳輸鲤嫡,性能更好,但是由于本地緩存使用應用進程的內存空間绑莺,不能進行大數據存儲暖眼。
- 集群數據更新問題:本地緩存只支持本地進程應用訪問,其他進程應用無法訪問纺裁,因此需要額外的機制來保證數據的一致性诫肠,實現復雜度高且容易出錯。比如通過redis或zookeeper實現分布式同步欺缘。
- 數據隨應用進程重啟而丟失栋豫。
適用場景
本地緩存適用于緩存只讀數據,如字典谚殊、統(tǒng)計類數據丧鸯,以及進程獨立數據,如本地長連接服務络凿。
本地緩存實現
- Java堆緩存
使用Java堆內存來緩存數據骡送。沒有對象的序列化和反序列化,是最快的緩存絮记。在編程中常用HashMap和ConcurrentHashMap來實現本地緩存摔踱。Java堆緩存應該避免大數據量緩存(可能導致GC停頓時間過長),同時可以使用軟引用/弱引用來緩存對象怨愤,可以使當內存不足時派敷,強制回收這部分對象,釋放內存撰洗。Java堆內存一般用于緩存存儲較熱的數據篮愉。 - memcached/ecache
ecache是基于java的開源高效的、進程內緩存解決方案差导。ecache輕量试躏、簡單,被廣泛應用于其他ORM框架數據緩存的底層實現(如Hinernate)设褐。
memcached和ecache實現原理類似颠蕴,基于K-V將數據緩存到內存,memcached支持多線程操作助析。相比ecache犀被,memcached更加靈活。 - caffeine
Caffeine是基于Java 8的高性能緩存庫外冀,可提供接近最佳的命中率寡键。Caffeine與ConcurrentMap類似,但是Caffeine與ConcurrentMap最根本的區(qū)別是雪隧,ConcurrentMap會保留添加到其中的所有元素西轩,直到將其明確刪除為止员舵,而Caffeine能自動的回收存儲的元素。
通過caffeine基準測試藕畔,可以看到caffeine在讀寫方面明顯優(yōu)與其他框架固灵,在緩存命中率上Caffeine也不同于Guava,采用了更為優(yōu)秀的Window TinyLfu算法劫流,該算法是在LRU的基礎上改進的版本巫玻。
分布式緩存
分布式緩存也叫進程外緩存,通常是獨立于應用部署祠汇,通過網絡進行緩存讀寫的數據傳輸仍秤。
分布式緩存優(yōu)缺點
- 支持大量數據存儲,不受應用進行重啟影響:分布式緩存是獨立部署的進程可很,擁有獨立的內存空間诗力,并且一般以集群的方式拓展,故而可以進行大數據儲存我抠。
- 數據集中存儲苇本,保證數據一致性:當應用采用集群部署時,集群每個節(jié)點通過統(tǒng)一的分布式緩存服務進行數據的讀寫操作菜拓,不存在本地緩存中數據更新問題瓣窄,保證不同節(jié)點應用進行的數據一致性問題。
- 數據讀寫分離纳鼎、高性能俺夕、高可用:分布式緩存一般支持數據副本機制,可以實現讀寫分離贱鄙,可以解決高并發(fā)場景中數據讀寫性能問題劝贸。并且由于在多節(jié)點緩存冗余數據,提高了存儲數據的可用性逗宁,避免某個節(jié)點宕機導致數據不可用映九。
- 數據基于網絡傳輸,性能低于本地緩存瞎颗。
分布式緩存實現
redis/memcached
分布式緩存一致性方案
緩存一致性問題主要是數據庫和緩存的讀寫一致性問題件甥。
首先給緩存設置過期時間是保證緩存最終一致性解決方案,所有數據的寫操作以數據庫為準言缤,對緩存盡最大努力嚼蚀。
緩存更新策略:
先更新數據庫再失效緩存
- 失效:應用先從緩存獲取數據禁灼,如果沒有從數據庫讀取管挟,成功后放入緩存。
- 命中:應用從緩存讀取數據弄捕,命中后返回
- 更新:先更新數據庫僻孝,然后失效緩存(延時雙刪)
緩存穿透及解決方案
緩存穿透是指key對應的數據源不存在导帝,導致每次針對key的請求都無法從存儲層獲取數據并寫入到緩存,從而每次請求都落到數據庫穿铆,失去了緩存意義您单。流量大時可能就拖垮了DB。
解決方案
- 方案一:對于查詢返回為空的數據荞雏,仍存儲到緩存(需要設置緩存過期時間盡可能短)
- 方案二:使用布隆過濾器虐秦,將可能存在的數據hash到足夠大bitmap中,一個一定不存在的數據可以通過布隆過濾器攔截掉凤优。
緩存擊穿及解決方案
緩存擊穿主要出現在高并發(fā)的熱點數據訪問場景悦陋。導致緩存擊穿原因主要是同一時間發(fā)生消除讀寫(刪),導致并發(fā)下緩存失效筑辨“呈唬或者是并發(fā)讀取緩存時,恰巧達到緩存失效還來不及從數據庫讀取并寫入緩存棍辕。
解決方案
- 比較常見的方法是使用使用互斥鎖(mutex)機制暮现,當緩存失效時,不是立即去load DB楚昭,而是先執(zhí)行set mutex(比如redis的setnx)栖袋,如果操作成功,則load DB并寫入緩存抚太,如果操作失敗則重試get緩存栋荸。
- 另外可以對某些靜態(tài)熱點數據使用永不過期策略。
緩存雪崩及解決方案
是指設置緩存相同的過期時間凭舶,如果一些應用初始加載緩存晌块,采用并發(fā)寫策略(多線程),導致了某一時間緩存全部失效帅霜,請求全部轉發(fā)到DB匆背,DB瞬時壓力過大雪崩。高并發(fā)應用的緩存雪崩對于底層系統(tǒng)沖擊非成砑剑可怕钝尸。
解決方案
- 考慮加鎖或者隊列方式寫緩存,避免緩存過期時間一致
- 在設置緩存是在原有緩存失效時間基礎加上一個隨機值搂根,降低過期時間的重復率珍促。