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