高併發場景下緩存處理思路總結

在實際的開發當中,我們經常需要進行磁盤數據的讀取和搜索,因此經常會有出現從數據庫讀取數據的場景出現。但是當數據訪問量次數增大的時候,過多的磁盤讀取可能會最終成爲整個系統的性能瓶頸,甚至是壓垮整個數據庫,導致系統卡死等嚴重問題。

常規的應用系統中,我們通常會在需要的時候對數據庫進行查找,因此係統的大致結構如下所示:

當數據量較高的時候,需要減少對於數據庫裏面的磁盤讀寫操作,因此通常都會選擇在業務系統和 MySQL 數據庫之間加入一層緩存從而減少對數據庫方面的訪問壓力。

但是很多時候,緩存在實際項目中的應用並非這麼簡單。下邊我們來通過幾個比較經典的幾個緩存應用場景來列舉一些問題:

1. 緩存和數據庫之間數據一致性問題

常用於緩存處理的機制我總結爲了以下幾種:

Cache Aside, Read Through, Write Through, Write Behind Caching

首先來簡單說說 Cache aside 的這種方式:

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 模式是指應用程序始終從緩存中請求數據。如果緩存沒有數據,則它負責使用底層提供程序插件從數據庫中檢索數據。檢索數據後,緩存會自行更新並將數據返回給調用應用程序。使用 Read Through 有一個好處。我們總是使用 key 從緩存中檢索數據, 調用的應用程序不知道數據庫, 由存儲方來負責自己的緩存處理,這使代碼更具可讀性, 代碼更清晰。但是這也有相應的缺陷,開發人員需要給編寫相關的程序插件,增加了開發的難度性。

Write Through 模式

Write Through 模式和 Read Through 模式類似,當數據發生更新的時候,先去 Cache 裏面進行更新,如果命中了,則先更新緩存再由 Cache 方來更新 database。如果沒有命中的話,就直接更新 Database 裏面的數據。

Write Behind Caching 模式

Write Behind Caching 這種模式通常是先將數據寫入到緩存裏面,然後再異步的寫入到 database 中進行數據同步,這樣的設計既可以直接的減少我們對於數據的 database 裏面的直接訪問,降低壓力,同時對於 database 的多次修改可以進行合併操作,極大的提升了系統的承載能力。

但是這種模式處理緩存數據具有一定的風險性,例如說當 cache 機器出現宕機的時候,數據會有丟失的可能。

2. 緩存穿透問題

在高併發的場景中,緩存穿透是一個經常都會遇到的問題。

什麼是緩存穿透?

大量的請求在緩存中沒有查詢到指定的數據,因此需要從數據庫中進行查詢,造成緩存穿透。

會造成什麼後果?

大量的請求短時間內湧入到 database 中進行查詢會增加 database 的壓力,最終導致 database 無法承載客戶單請求的壓力,出現宕機卡死等現象。

常用的解決方案通常有以下幾類:

1. 空值緩存

在某些特定的業務場景中,對於數據的查詢可能會是空的,沒有實際的存在,並且這類數據信息在短時間進行多次的反覆查詢也不會有變化,那麼整個過程中,多次的請求數據庫操作會顯得有些多餘。不妨可以將這些空值(沒有查詢結果的數據)對應的 key 存儲在緩存中,那麼第二次查找的時候就不需要再次請求到 database 那麼麻煩,只需要通過內存查詢即可。這樣的做法能夠大大減少對於 database 的訪問壓力。

2. 布隆過濾器

通常對於 database 裏面的數據的 key 值可以預先存儲在布隆過濾器裏面去,然後先在布隆過濾器裏面進行過濾,如果發現布隆過濾器中沒有的話,就再去 redis 裏面進行查詢,如果 redis 中也沒有數據的話,再去 database 查詢。這樣可以避免不存在的數據信息也去往存儲庫中進行查詢情況。

關於布隆過濾器的學習可以參考下我的這篇筆記:

https://blog.csdn.net/Danny_idea/article/details/88946673

3. 緩存雪崩場景

什麼是緩存雪崩?

當緩存服務器重啓或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給後端系統 (比如 DB) 帶來很大壓力。

如何避免緩存雪崩問題?

1. 使用加鎖隊列來應付這種問題。 當有多個請求湧入的時候,當緩存失效的時候加入一把分佈式鎖,只允許搶鎖成功的請求去庫裏面讀取數據然後將其存入緩存中,再釋放鎖,讓後續的讀請求從緩存中取數據。但是這種做法有一定的弊端,過多的讀請求線程堵塞,將機器內存佔滿,依然沒有能夠從根本上解決問題。

2. 在併發場景發生前,先手動觸發請求,將緩存都存儲起來,以減少後期請求對 database 的第一次查詢的壓力。數據過期時間設置儘量分散開來,不要讓數據出現同一時間段出現緩存過期的情況。

3. 從緩存可用性的角度來思考,避免緩存出現單點故障的問題, 可以結合使用 主從 + 哨兵的模式來搭建緩存架構,但是這種模式搭建的緩存架構有個弊端,就是無法進行緩存分片,存儲緩存的數據量有限制,因此可以升級爲 Redis Cluster 架構來進行優化處理。(需要結合企業實際的經濟實力,畢竟 Redis Cluster 的搭建需要更多的機器)

4.Ehcache 本地緩存 + Hystrix 限流 & 降級, 避免 MySQL 被打死。

使用 Ehcache 本地緩存的目的也是考慮在 Redis Cluster 完全不可用的時候,Ehcache 本地緩存還能夠支撐一陣。

使用 Hystrix 進行限流 & 降級 ,比如一秒來了 5000 個請求,我們可以設置假設只能有一秒 2000 個請求能通過這個組件,那麼其他剩餘的 3000 請求就會走限流邏輯。

然後去調用我們自己開發的降級組件(降級),比如設置的一些默認值呀之類的。以此來保護最後的 MySQL 不會被大量的請求給打死。

作者:Danny_idea

來源:https://blog.csdn.net/Danny_idea/article/details/91347674

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/drVuTQePfYfnFfYq4L1xlg