轉載自:微信公衆號《Java知音》
在實際的開發當中,咱們常常須要進行磁盤數據的讀取和搜索,所以常常會有出現從數據庫讀取數據的場景出現。redis
可是當數據訪問量次數增大的時候,過多的磁盤讀取可能會最終成爲整個系統的性能瓶頸,甚至是壓垮整個數據庫,致使系統卡死等嚴重問題。數據庫
常規的應用系統中,咱們一般會在須要的時候對數據庫進行查找,所以系統的大體結構以下所示:後端
當數據量較高的時候,須要減小對於數據庫裏面的磁盤讀寫操做,所以一般都會選擇在業務系統和MySQL數據庫之間加入一層緩存從而減小對數據庫方面的訪問壓力。緩存
可是不少時候,緩存在實際項目中的應用並不是這麼簡單。下邊咱們來經過幾個比較經典的緩存應用場景來列舉一些問題:服務器
經常使用於緩存處理的機制我總結爲了如下幾種:微信
Cache Aside
Read Through
Write Through
Write Behind Caching
首先來簡單說說Cache aside的這種方式:架構
這種模式處理緩存一般都是先從數據庫緩存查詢,若是緩存沒有命中則從數據庫中進行查找。併發
這裏面會發生的三種狀況以下:異步
緩存命中:分佈式
當查詢的時候發現緩存存在,那麼直接從緩存中提取。
緩存失效:
當緩存沒有數據的時候,則從database裏面讀取源數據,再加入到cache裏面去。
緩存更新:
當有新的寫操做去修改database裏面的數據時,須要在寫操做完成以後,讓cache裏面對應的數據失效。
這種Cache aside模式一般是咱們在實際應用開發中最爲經常使用到的模式。可是並不是說這種模式的緩存處理就必定能作到完美。
關於這種模式下依然會存在缺陷。
好比,一個是讀操做,可是沒有命中緩存,而後就到數據庫中取數據,此時來了一個寫操做,寫完數據庫後,讓緩存失效,而後,以前的那個讀操做再把老的數據放進去,因此,會形成髒數據。
Facebook的大牛們也曾經就緩存處理這個問題發表過相關的論文,連接以下:
https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final170_update.pdf
分佈式環境中要想徹底的保證數據一致性是一件極爲困難的事情,咱們只可以儘量的減低這種數據不一致性問題產生的狀況。
Read Through模式是指應用程序始終從緩存中請求數據。若是緩存沒有數據,則它負責使用底層提供程序插件從數據庫中檢索數據。檢索數據後,緩存會自行更新並將數據返回給調用應用程序。使用Read Through 有一個好處。
咱們老是使用key從緩存中檢索數據, 調用的應用程序不知道數據庫, 由存儲方來負責本身的緩存處理,這使代碼更具可讀性, 代碼更清晰。
可是這也有相應的缺陷,開發人員須要給編寫相關的程序插件,增長了開發的難度性。
Write Through模式和Read Through模式相似,當數據發生更新的時候,先去Cache裏面進行更新,若是命中了,則先更新緩存再由Cache方來更新database。若是沒有命中的話,就直接更新Cache裏面的數據。
Write Behind Caching 這種模式一般是先將數據寫入到緩存裏面,而後再異步的寫入到database中進行數據同步
這樣的設計既能夠直接的減小咱們對於數據的database裏面的直接訪問,下降壓力,同時對於database的屢次修改能夠進行合併操做,極大的提高了系統的承載能力。
可是這種模式處理緩存數據具備必定的風險性,例如說當cache機器出現宕機的時候,數據會有丟失的可能。
在高併發的場景中,緩存穿透是一個常常都會遇到的問題。
什麼是緩存穿透?
大量的請求在緩存中沒有查詢到指定的數據,所以須要從數據庫中進行查詢,形成緩存穿透。
會形成什麼後果?
大量的請求短期內涌入到database中進行查詢會增長database的壓力,最終致使database沒法承載客戶單請求的壓力,出現宕機卡死等現象。
經常使用的解決方案一般有如下幾類:
在某些特定的業務場景中,對於數據的查詢可能會是空的,沒有實際的存在,而且這類數據信息在短期進行屢次的反覆查詢也不會有變化,那麼整個過程當中,屢次的請求數據庫操做會顯得有些多餘。
不妨能夠將這些空值(沒有查詢結果的數據)對應的key存儲在緩存中,那麼第二次查找的時候就不須要再次請求到database那麼麻煩,只須要經過內存查詢便可。這樣的作法可以大大減小對於database的訪問壓力。
一般對於database裏面的數據的key值能夠預先存儲在布隆過濾器裏面去,而後先在布隆過濾器裏面進行過濾
若是發現布隆過濾器中沒有的話,就再去redis裏面進行查詢,若是redis中也沒有數據的話,再去database查詢。這樣能夠避免不存在的數據信息也去往存儲庫中進行查詢狀況。
關於布隆過濾器的學習能夠參考下個人這篇筆記:
https://blog.csdn.net/Danny_idea/article/details/88946673
什麼是緩存雪崩?
當緩存服務器重啓或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給後端系統(好比DB)帶來很大壓力。
如何避免緩存雪崩問題?
1.使用加鎖隊列來應付這種問題。當有多個請求涌入的時候,當緩存失效的時候加入一把分佈式鎖,只容許搶鎖成功的請求去庫裏面讀取數據而後將其存入緩存中,再釋放鎖,讓後續的讀請求從緩存中取數據。
可是這種作法有必定的弊端,過多的讀請求線程堵塞,將機器內存佔滿,依然沒有可以從根本上解決問題。
2.在併發場景發生前,先手動觸發請求,將緩存都存儲起來,以減小後期請求對database的第一次查詢的壓力。
數據過時時間設置儘可能分散開來,不要讓數據出現同一時間段出現緩存過時的狀況。
3.從緩存可用性的角度來思考,避免緩存出現單點故障的問題,能夠結合使用 主從+哨兵的模式來搭建緩存架構
可是這種模式搭建的緩存架構有個弊端,就是沒法進行緩存分片,存儲緩存的數據量有限制,所以能夠升級爲Redis Cluster架構來進行優化處理
(須要結合企業實際的經濟實力,畢竟Redis Cluster的搭建須要更多的機器)
4.Ehcache本地緩存 + Hystrix限流&降級,避免MySQL被打死。
使用 Ehcache本地緩存的目的也是考慮在 Redis Cluster 徹底不可用的時候,Ehcache本地緩存還可以支撐一陣。
使用 Hystrix進行限流 & 降級 ,好比一秒來了5000個請求,咱們能夠設置假設只能有一秒 2000個請求能經過這個組件,那麼其餘剩餘的 3000 請求就會走限流邏輯。
而後去調用咱們本身開發的降級組件(降級),好比設置的一些默認值呀之類的。以此來保護最後的 MySQL 不會被大量的請求給打死。