缓存层设计套路(一)
一、背景
對于傳統的后端業務場景(或者單機應用)中,訪問量以及對響應時間的要求均不高,通常只使用DB即可滿足要求。這種架構簡單,便于快速部署,很多網站發展初期均考慮使用這種架構。但是隨著訪問量的上升,以及對響應時間的要求提升,單DB無法再滿足要求。這時候通常會考慮DB拆分(sharding)、讀寫分離、甚至硬件升級(SSD)等以滿足新的業務需求。但是這種方式仍然會面臨很多問題,主要體現在:
?
1、性能提升有限,很難達到數量級上的提升,尤其在互聯網業務場景下,隨著網站的發展,訪問量經常會面臨十倍、百倍的上漲。
2、成本高昂,為了承載N倍的訪問量,通常需要掛載更多的只讀庫,或者升級數據庫實例的規格。
?
在計算機科學領域中有一句話:任何問題都可以通過增加一個間接的中間層來解決。本次的分享正是介紹解決以上問題的一個中間層——緩存層設計。
????????
二、前言
鑒于緩存層的設計異常的復雜,需要考慮的問題很多,諸如:更新策略,緩存穿透,緩存一致性,緩存并發,緩存雪崩等。
本次只涉及到緩存的更新策略部分。
?
三、緩存層鳥瞰圖
?
?
如上圖所示,為了解決數據庫性能瓶頸問題,對于讀多寫少的數據查詢,可以通過多架設一層緩存層來減少對DB的直接訪問。由于一般緩存中間件(redis、memcached)的key-value對都是常駐內存的,所以如果能直接命中緩存,一來可以極大的提高網站的響應速度,二來也可以大幅地減少直接對數據庫的操作。
緩存層的工作原理一般分為以下兩步:
1??? 當應用發起查詢請求時,可以先通過查詢緩存中的數據,如果命中緩存結果即可馬上響應請求。
2??? 如果沒有命中緩存,或者緩存已經失效了,則需要直接查詢數據庫,再次將結果緩存起來,如果響應請求,返回數據。
?
?
四、緩存更新策略
有了以上基本了解,我們進入到本次分享的主題——緩存更新策略。
?
首先思考一下,為什么會有緩存更新策略的問題,這個策略需要解決的又是什么問題?
?
?
?
緩存層是解決數據庫性能的一個中間層,既然是中間層,那么引入緩存層當然不能影響以前正常的業務操作。這里就引出了一個問題,就是如何確保緩存層中的數據與數據庫中數據的一致性問題。緩存更新策略正是為了處理數據一致性的問題而誕生的。
?
?
緩存更新的模式有四種:Cache aside,Read through,Write through,Write behind caching。
?
1、?Cache aside(緩存預留)
這是最常用最常用的策略。其具體邏輯如下:
失效:應用程序先從cache取數據,沒有得到,則從數據庫中取數據,成功后,放到緩存中。
命中:應用程序從cache中取數據,取到后返回。
更新:先把數據存到數據庫中,成功后,再讓緩存失效。
?
2、Read/Write Through (直接讀/寫)
Read Through 就是在查詢操作中更新緩存,也就是說,當緩存失效或過期的時候,Cache Aside是由調用方負責把數據加載入緩存,而Read Through則用緩存服務自己來加載,從而對應用方是透明的。
Write Through 和Read Through類似,不過是在更新數據時發生。當有數據更新的時候,如果沒有命中緩存,直接更新數據庫,然后返回。如果命中了緩存,則更新緩存,然后再由Cache自己更新數據庫(這是一個同步操作)。
我們可以看到,在上面的Cache Aside中,我們的應用代碼需要維護兩個數據存儲,一個是緩存(Cache) ,一個是數據庫(Repository)。所以,應用程序比較難維護。而Read/Write Through是把更新數據庫(Repository)的操作由緩存自己代理了,所以,對于應用層來說,就簡單很多了。可以理解為,應用認為后端就是一個單一的存儲,而存儲自己維護自己的Cache。
?
/3、?Write Behind Caching(回寫)
在更新數據的時候,只更新緩存,不更新數據庫,而我們的緩存會異步地批量更新數據庫。這個設計的好處就是讓數據的I/O操作飛快無比(因為直接操作內存) ,因為異步,Write Behind Caching還可以合并對同一個數據的多次操作,所以性能的提高是相當可觀的。
Write Behind Caching實現邏輯比較復雜,因為他需要追蹤有哪數據是被更新了的,需要刷到持久層上。
?
但是,其帶來的問題是,數據不是強一致性的,而且可能會丟失(Unix/Linux非正常關機會導致數據丟失,就是因為這個原因,因為Linux文件系統的Page Cache的算法使用的就是write back,類似于Write Behind Caching)。在軟件設計上,我們基本上不可能做出一個沒有缺陷的設計,就像算法設計中的時間換空間,空間換時間一個道理,有時候,強一致性和高性能,高可用和高性性是有沖突的。
?
?
?o?思考
?
思考如下場景:
1、用戶A將商品S的售價從50修改為100
2、同一時間用戶B在進行開單操作
?
這種情況下如何確保用戶B在出售商品S的時候,售價是100呢?
?
使用上述的緩存更新策略,是否能解決這個場景問題。
?
還是不能的,比如,一個是讀操作,但是沒有命中緩存,然后就到數據庫中取數據,此時來了一個寫操作,寫完數據庫后,讓緩存失效,然后,之前的那個讀操作再把老的數據放進去,所以,會造成臟數據。
?
但,這個案例理論上會出現,不過,實際上出現的概率可能非常低,因為這個條件需要發生在讀緩存時緩存失效,而且并發著有一個寫操作。而實際上數據庫的寫操作會比讀操作慢得多,而且還要鎖表,而讀操作必需在寫操作前進入數據庫操作,而又要晚于寫操作更新緩存,所有的這些條件都具備的概率基本并不大。
?
所以,要么通過2PC或是Paxos協議保證一致性,要么就是拼命的降低并發時臟數據的概率,而Facebook使用了這個降低概率的這種方法,因為2PC太慢,而Paxos太復雜。當然,最好還是為緩存設置上過期時間。
總結
以上是生活随笔為你收集整理的缓存层设计套路(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASA IPSEC ***配置
- 下一篇: 『高级篇』docker之安全认证kube