緩存能夠說是無處不在,好比PC電腦中的內存、CPU中的二級緩存、HTTP協議中的緩存控制、CDN加速技術都是使用了緩存的思想來解決性能問題。數據庫
緩存是用於解決高併發場景下系統的性能及穩定性問題的銀彈。後端
1.1 緩存穿透緩存
緩存穿透是指查詢一個根本不存在的數據,緩存和數據源都不會命中。出於容錯的考慮,若是從數據層查不到數據則不寫入緩存,即數據源返回值爲null時,不緩存null。緩存穿透問題可能會使後端數據負載加大,因爲不少後端數據源不具有高併發性,甚至可能形成後端數據源宕機。性能優化
爲了解決這個問題,通常建議(1)減小回源請求併發數,下降數據源的負載(2)將null值進行緩存,可是爲了不數據不一致和沒必要要的內存佔用,建議縮短緩存過時時間,並增長相關的主動刪除緩存功能。服務器
(1)緩存系統主要消耗的是服務器的內存,所以,在使用緩存時必需要先對應用須要緩存的數據大小進行評估,包括緩存的數據結構、緩存大小、緩存數量、緩存的失效時間,而後根據業務狀況自行推算在將來一段時間內的容量的使用狀況,根據容量評估的結果來申請和分配緩存資源,不然會形成資源浪費或者緩存空間不夠。數據結構
(2)緩存通常是用來加速數據庫的讀操做的,通常先訪問緩存後訪問數據庫,因此緩存的超時時間的設置是很重要的。筆者曾經在一家互聯網公司遇到過因爲運維操做失誤致使緩存超時設置得較長,從而拖垮服務的線程池,最終致使服務雪崩的狀況。架構
(3)任何緩存的key都必須設定緩存失效時間,且失效時間不能集中在某一點(能夠經過增長隨機時間),不然會致使緩存佔滿內存或者緩存雪崩。併發
(4)低頻訪問的數據不要放在緩存中,如咱們前面所說的,咱們使用緩存的主要目的是提升讀取性能。框架
(5)緩存的數據不易過大,尤爲是Redis,由於Redis使用的是單線程模型,在單個緩存key的數據過大時,會阻塞其餘請求的處理。運維
(6)對於存儲較多value的key,儘可能不要使用HGETALL等集合操做,該操做會形成請求阻塞,影響其餘應用的訪問。
(7)緩存通常用於在交易系統中加速查詢的場景,有大量的更新數據時,尤爲是批量處理時,請使用批量模式,可是這種場景較少。
(8)在一般狀況下,讀的順序是先緩存,後數據庫;寫的順序是先數據庫,後緩存。
(9)在使用本地緩存(如Ehcache)時,必定要嚴格控制緩存對象的個數及聲明週期。因爲JVM的特性,過多的緩存對象會極大影響JVM的性能,甚至致使內存溢出等。
(1)若是對性能的要求不是很是高,則儘可能使用分佈式緩存,而不要使用本地緩存,由於本地緩存在服務的各個節點之間複製,在某一時刻副本之間是不一致的,若是這個緩存表明的是開關,並且分佈式系統中的請求有可能會重複,就會致使重複的請求走到兩個節點,一個節點的開關是開,一個節點的開關是關,若是請求處理沒有作到冪等,就會形成處理重複,在嚴重狀況下會形成資金損失。
(1)建議將使用緩存的業務進行分離,核心業務和非核心業務使用不一樣的緩存實例,從物理上進行隔離,若是有條件,則請對每一個業務使用單獨的實例或集羣,以減小應用之間相互影響的可能性。筆者就常常據說有的公司應用了共享數據,形成緩存數據被覆蓋以及緩存數據錯亂的線上事故。
(2)咱們不推薦多個業務共享一個緩存實例,可是因爲成本控制的緣由,這種狀況常常出現,咱們須要經過規範來限制各個應用使用的key有惟一的前綴,並進行隔離設計,避免產生緩存互相覆蓋的問題。
(3)在寫緩存時必定要寫入徹底正確的數據,若是緩存數據的一部分有效、一部分無效,則寧肯放棄緩存,也不要把部分數據寫入緩存,不然會形成空指針、程序異常等。
(4)在使用緩存時,必定要有降級處理,尤爲是對關鍵的業務環節,緩存有問題或者失效時也要能回源到數據庫進行處理。
(1)全部的緩存實例都須要添加監控,這是很是重要的,咱們須要對慢查詢、大對象、內存使用狀況作可靠的監控。
緣由:在應用程序中對使用的大量緩存key設置了同一個固定的失效時間,當緩存失效時,會形成在一段時間內同時訪問數據庫,形成數據庫的壓力較大。
總結:在使用緩存時須要進行緩存設計,要充分考慮如何避免常見的緩存穿透、緩存雪崩、緩存併發等問題,尤爲是對於高併發的緩存使用,須要對key的過時時間進行隨機設置,例如,將過時時間設置爲10秒+random(2),也就是將過時時間隨機設置成10~12秒。
緣由:在遷移的過程當中,重複的流量進入了不一樣的節點,因爲使用了本地緩存存儲遷移開關,而遷移開關在開關打開的瞬間致使各個節點的開關狀態不一致,有的是開、有的是關,因此對於不一樣節點的流量的處理重複,一個走了開關開的邏輯,一個走了開關關的邏輯。
總結:避免使用本地緩存來存儲遷移開關,遷移開關應該在有狀態的訂單上標記。
緣由:因爲這個模塊的使用方查詢請求的數據在數據庫中不存在,是非法的數據,因此致使緩存沒有命中,每次都穿透到數據庫,且量級較大。
總結:在使用緩存時須要進行緩存設計,要充分考慮如何避免常見的緩存穿透、緩存雪崩、緩存併發等問題,尤爲是對高併發的緩存使用,須要對無效的key進行緩存,以抵擋惡意的或者無心的對無效緩存查詢的攻擊或影響。
緣由:應用系統使用了哈希鍵,哈希鍵自己有過時時間,可是哈希鍵裏面的每一個鍵值對沒有過時時間。
總結:在設計Redis的過程當中,若是有大量的鍵值對要保存,則請使用字符串鍵的數據庫類型,並對每一個鍵都設置過時時間,請不要在哈希鍵內部存儲一個沒有邊界的集合數據。實際上,不管是對緩存、內存仍是對數據庫的設計,若是使用任意一個集合的數據結構,則都要考慮爲它設置最大限制,避免內存用光,最多見的是集合溢出致使的內存溢出的問題。
緣由:Redis進行主備切換,致使瞬間內應用鏈接Redis異常,應用並無對緩存作降級處理。
總結:對於核心業務,在使用緩存時必定要有降級方案。常見的降級方案是在數據庫層次預留足夠的容量,在某一部分緩存出現問題時,可讓應用暫時回源到數據庫繼續業務邏輯,而不該該中斷業務邏輯,可是這須要嚴格的容量評估,請參考《分佈式服務架構:原理設計與實戰》第3章的內容。
緣由:由於這個項目是個歷史項目,使用了Hibernate ORM框架,在Hibernate中開啓了二級緩存,使用了Ehcache;可是在Ehcache中沒有控制緩存對象的個數,緩存對象增多,致使內存緊張,因此進行了頻繁的GC操做。
總結:使用本地緩存(如Ehcache、OSCache、應用內存)時,必定要嚴格控制緩存對象的個數及聲明週期。
現象:某個正常運行的應用忽然報警線程數太高,以後很快就出現了內存溢出。
緣由:因爲緩存鏈接數達到最大限制,應用沒法鏈接緩存,而且超時時間設置得較大,致使訪問緩存的服務都在等待緩存操做返回,因爲緩存負載較高,處理不完全部的請求,可是這些服務都在等待緩存操做返回,服務這時在等待,並無超時,就不能降級並繼續訪問數據庫。這在BIO模式下線程池就會撐滿,使用方的線程池也都撐滿;在NIO模式下同樣會使服務的負載增長,服務響應變慢,甚至使服務被壓垮。
總結:在使用遠程緩存(如Redis、Memcached)時,必定要對操做超時時間進行設置,這是很是關鍵的,通常咱們設計緩存做爲加速數據庫讀取的手段,也會對緩存操做作降級處理,所以推薦使用更短的緩存超時時間,若是必定要給出一個數字,則但願是100毫秒之內。
緣由:開發人員不知道如何發現、排查、定位和解決緩存問題。
總結:在設計緩存時要有降級方案,在遇到問題時首先使用降級方法,還要設計完善的監控和報警功能,幫助開發人員快速發現緩存問題,進而來定位和解決問題。
緣由:該應用的緩存key與其餘應用緩存 key衝突,致使互相覆蓋,出現邏輯錯誤。
總結:在使用緩存時必定要有隔離的設計,能夠經過不一樣的緩存實例來作物理隔離,也能夠經過各個應用的緩存key使用不一樣的前綴進行邏輯隔離。
http://www.sohu.com/a/224864978_411876