天天看點

緩存,确實很香,卻也很受傷!

問一下你自己,最直接的使用緩存的原因是什麼?

無它,唯快而已!

追溯一下自己最開始使用緩存的場景,一些資料庫裡存儲的不變的配置資訊,服務啟動時,直接加載到本地公共子產品,友善其它功能子產品共享使用。這便是最基本,最簡單的本地緩存應用。

一、服務與緩存

所謂的服務,簡而言之,一層應用 + 一層資料,應用從資料層擷取資料然後加工輸出。

資料層,通常我們指的是持久化媒體上的持久化存儲。它有多種形式的,可以是檔案,或者資料庫。

資料存儲在持久化媒體上,而應用運作與記憶體中。記憶體和持久化媒體是兩個有着量級速度差别的不同媒體,由此,應用和資料之間便有了“沖突”。

有了這“沖突”的引子,便有了對緩存的迫切需求。

我們說的緩存,必然要是存放于記憶體中的,這樣它便能距離應用更近,更快的給出應用所需要的資料,以獲得更快的服務響應

緩存,确實很香,卻也很受傷!

當然,并不是緩存完全隔絕持久層資料。緩存,伴随而生的一個詞叫做命中率。

當我們查詢的資料存在于緩存中的時候,我們稱之為“命中”,此時,所需資料可以直接由緩存提供。

而對于未“命中”的資料,則需要穿過緩存層,進一步去持久化資料層擷取。此種情景,我們稱之為緩存穿透。

資料擷取之後,在傳回給應用之前,我們需要重新填充緩存,以供下一次“命中”查詢。

當然,上述我們所述隻是指“讀”查詢情景。

當應用發生資料操作變更,我們則需要将變更同時更新到持久層及緩沖層。此時,我們又會面臨另外一個問題,“先”與“後” 的問題。

緩存,确實很香,卻也很受傷!

“先”與“後”的問題,我們也稱之為緩存一緻性問題。

如果先更新緩存,則可能面臨持久層更新失敗,産生緩存髒資料的問題。

然則,假如先更新持久層,我們又不得不面對從持久層更新成功之後到緩存更新之前這個間期,緩存對外提供舊資料的窘境。

緩存一緻性問題,尤其在高并發環境,需要根據特定場景進行更精妙的控制。

比如,并發修改的一緻性鎖;比如,異步重新整理的延遲重新整理等等。

二、緩存與更新

上面我們提到了緩存更新一緻性的問題,從實際應用情景來講,可以細分為強一緻性需求,弱一緻性需求及最終一緻性需求。

1、強一緻性需求

比如,交易狀态資訊,已下單、支付中,已支付等應用,需要我們主動及時進行關聯更新并保證事務層面的一緻性。

應景而生的許多包括分布式事務等理論也為我們解決實際問題提供了很好的踐行方案。

2、弱一緻性需求

一些涉及不太重要的資訊更新,能夠容忍短時間(比如,幾分鐘)内持久層資料和緩存資料不一緻的場景。比如不外顯的描述資訊,統計性的計數緩存資訊等。通常可以采取異步處理的方式。

一些一段短時間内(幾秒,幾分鐘)輸出固定資訊的場景。比如每隔30s更新熱點資訊,票價資訊等。可以通過設定緩存逾時自動剔除的方式進行處理。

3、最終一緻性需求

保障資料狀态的最終一緻性。

三、緩存的粒度

所謂粒度,也即緩存資訊塊層級,大小。選擇何種粒度的緩存,取決于我們應用的整體架構,資料存儲規劃及具體的應用場景。

拿使用者資訊來舉例,是緩存活躍資訊?還是相對靜态的資訊?是按單屬性層級來緩存?還是按整個對象資訊?

不同的資料粒度,也決定着我們存儲緩存的形式:整個對象的二進制序列化資料?更透明直覺的json字元串?屬性與值的一一映射?

每種形式都有各自的使用優缺點,開發者可以從應用、存儲及維護成本各方面進行全面性評估選擇。另外,關注公衆号Java技術棧,在背景回複:面試,可以擷取我整理的緩存系列面試題和答案,非常齊全。

四、緩存穿透的危害

第一小節,我們提到過關于緩存穿透發生的原因:緩存未命中。那為什麼會未命中呢?

1、資料暫時不存在于緩存中

所謂暫時,可以指資料初始尚未加載到緩存,lazy load 按需按時時事加載應用;

也可以是緩存資料被我們特定的緩存過期政策自動或主動過期,通常使用的過期政策包括元素數量限制,記憶體占用限制及生存時間限制。

緩存,确實很香,卻也很受傷!

其實,無論是初始未加載還是緩存過期,删除,這些都屬于我們假定的正常應用場景,再次我們不予過多評論。

2、資料從來不存在

當一個查詢不存在資料的請求到來,其必然會穿過緩存,達到持久化存儲層。

持久話存儲的響應能力是有限的,當這種請求達到一定的量級,服務可能就要面臨着當機的危險。

至此,我們對于緩存的作用認知,也需要進一步延伸:降低下層負載,保護後端資源。

緩存,确實很香,卻也很受傷!

造成這種緩存穿透的原因可以簡單的分為内外兩方面誘因:内部的應用邏輯問題及外部惡意攻擊、爬蟲幹擾等。

内部問題容易解決,内觀可預知,良性優化即可;

反而是外部的不可預料,可能需要更謹慎的進行多面的防禦性處理。

其實,不論内部還是外部,在緩存層面需要處理的就隻有一件事:有效攔截穿透。

到此,通常慣性的思維第一步,就是把造成緩存穿透的資料放置到緩存中,無論其是否存在在于持久化存儲中。

比如對于正常的已删除的使用者資料,做緩存層面的軟删除處理,以狀态資訊做标注(我存在,其實我不存在! 😳)。就可以很好的解決此類問題造成的穿透壓力。

但是,我們有也個清楚的認知就,就是真正能夠造成危害的是那些非正常的入侵資料。比如,窮盡周遊的差别資料,一一存入緩存,唯一的結果就是緩存資源的溢滿用盡。這是一種相當恐怖的場景。

針對此種“大資料”型攻擊,布隆過濾攔截或許可以成為一個不錯的選擇。

緩存,确實很香,卻也很受傷!

五、也談緩存雪崩

上面一節中我們談到了緩存的承載保護功能,一面快速響應,一面背負保護持久層資料。

在某些以讀為主的服務中,緩存幾近承載近乎90%以上的請求。

但是,如果緩存由于某些原因一時不能提供正常服務時,所有的請求就會穿透到持久存儲層,造成存儲層極端當機情況發生。

緩存,确實很香,卻也很受傷!

那麼,我們應該如何應對這種情況呢?

1、高可用

緩存的高可用是應對緩存雪崩的首要保障:主從,讀寫分離,動态擴容,一緻性均衡,異地容災等。

實際應用如Redis的哨兵模式,叢集部署等。

緩存,确實很香,卻也很受傷!

2、服務治理之限流、熔斷降級

服務治理的目的是什麼?服務的穩定性。

限流即對異常流量的控制;熔斷、降級标的核心服務資源的保護。

筆者在輕量級熔斷降級架構 alibaba sentinel 應用介紹過當下流行的幾種流控架構的使用。

緩存、持久化資料存儲都是資源,或者我們可以從對緩存的流控及對持久化資料存儲的熔斷、降級保護來着手應對緩存雪崩的情景發生。

緩存,确實很香,卻也很受傷!

3、緩存元素的集中過期導緻緩存失效

對于設定了過期時間的緩存元素,如果發生元素同時過期,則會有瞬間的外部請求直接到達持久存儲層。

在實際的緩存應用中,需要采取一定的措施,實作緩存元素過期時間的均勻分布。另外,關注公衆号Java技術棧,在背景回複:面試,可以擷取我整理的緩存系列面試題和答案,非常齊全。