13 緩存的使用姿勢(一):如何選擇緩存的讀寫策略?

上節(jié)課刽肠,我?guī)懔私饬司彺娴亩x、分類以及不足免胃,你現(xiàn)在應(yīng)該對緩存有了初步的認知音五。從今天開始,我將帶你了解一下使用緩存的正確姿勢羔沙,比如緩存的讀寫策略是什么樣的躺涝,如何做到緩存的高可用以及如何應(yīng)對緩存穿透。通過了解這些內(nèi)容扼雏,你會對緩存的使用有深刻的認識坚嗜,這樣在實際工作中就可以在緩存使用上游刃有余了。

今天诗充,我們先講講緩存的讀寫策略苍蔬。你可能覺得緩存的讀寫很簡單,只需要優(yōu)先讀緩存蝴蜓,緩存不命中就從數(shù)據(jù)庫查詢碟绑,查詢到了就回種緩存。實際上茎匠,針對不同的業(yè)務(wù)場景格仲,緩存的讀寫策略也是不同的。

而我們在選擇策略時也需要考慮諸多的因素诵冒,比如說凯肋,緩存中是否有可能被寫入臟數(shù)據(jù),策略的讀寫性能如何汽馋,是否存在緩存命中率下降的情況等等侮东。接下來圈盔,我就以標準的“緩存 + 數(shù)據(jù)庫”的場景為例,帶你剖析經(jīng)典的緩存讀寫策略以及它們適用的場景苗桂。這樣一來韵卤,你就可以在日常的工作中根據(jù)不同的場景選擇不同的讀寫策略厉碟。

Cache Aside(旁路緩存)策略

我們來考慮一種最簡單的業(yè)務(wù)場景,比方說在你的電商系統(tǒng)中有一個用戶表,表中只有 ID 和年齡兩個字段氯葬,緩存中我們以 ID 為 Key 存儲用戶的年齡信息。那么當(dāng)我們要把 ID 為 1 的用戶的年齡從 19 變更為 20歪架,要如何做呢吉捶?

你可能會產(chǎn)生這樣的思路:先更新數(shù)據(jù)庫中 ID 為 1 的記錄,再更新緩存中 Key 為 1 的數(shù)據(jù)放案。

image.png

這個思路會造成緩存和數(shù)據(jù)庫中的數(shù)據(jù)不一致姚建。比如,A 請求將數(shù)據(jù)庫中 ID 為 1 的用戶年齡從 19 變更為 20吱殉,與此同時掸冤,請求 B 也開始更新 ID 為 1 的用戶數(shù)據(jù),它把數(shù)據(jù)庫中記錄的年齡變更為 21友雳,然后變更緩存中的用戶年齡為 21稿湿。緊接著,A 請求開始更新緩存數(shù)據(jù)押赊,它會把緩存中的年齡變更為 20饺藤。此時,數(shù)據(jù)庫中用戶年齡是 21流礁,而緩存中的用戶年齡卻是 20涕俗。

image.png

為什么產(chǎn)生這個問題呢?因為變更數(shù)據(jù)庫和變更緩存是兩個獨立的操作神帅,而我們并沒有對操作做任何的并發(fā)控制再姑。那么當(dāng)兩個線程并發(fā)更新它們的時候,就會因為寫入順序的不同造成數(shù)據(jù)的不一致枕稀。

另外询刹,直接更新緩存還存在另外一個問題就是丟失更新。還是以我們的電商系統(tǒng)為例萎坷,假如電商系統(tǒng)中的賬戶表有三個字段:ID凹联、戶名和金額,這個時候緩存中存儲的就不只是金額信息哆档,而是完整的賬戶信息了蔽挠。當(dāng)更新緩存中賬戶金額時,你需要從緩存中查詢完整的賬戶數(shù)據(jù),把金額變更后再寫入到緩存中澳淑。

這個過程中也會有并發(fā)的問題比原,比如說原有金額是 20,A 請求從緩存中讀到數(shù)據(jù)杠巡,并且把金額加 1量窘,變更成 21,在未寫入緩存之前又有請求 B 也讀到緩存的數(shù)據(jù)后把金額也加 1氢拥,也變更成 21蚌铜,兩個請求同時把金額寫回緩存,這時緩存里面的金額是 21嫩海,但是我們實際上預(yù)期是金額數(shù)加 2冬殃,這也是一個比較大的問題。

那我們要如何解決這個問題呢叁怪?其實审葬,我們可以在更新數(shù)據(jù)時不更新緩存,而是刪除緩存中的數(shù)據(jù)奕谭,在讀取數(shù)據(jù)時涣觉,發(fā)現(xiàn)緩存中沒了數(shù)據(jù)之后,再從數(shù)據(jù)庫中讀取數(shù)據(jù)血柳,更新到緩存中旨枯。

image.png

這個策略就是我們使用緩存最常見的策略,Cache Aside 策略(也叫旁路緩存策略)混驰,這個策略數(shù)據(jù)以數(shù)據(jù)庫中的數(shù)據(jù)為準,緩存中的數(shù)據(jù)是按需加載的皂贩。它可以分為讀策略和寫策略栖榨,其中讀策略的步驟是:

  • 從緩存中讀取數(shù)據(jù);
  • 如果緩存命中明刷,則直接返回數(shù)據(jù)婴栽;
  • 如果緩存不命中,則從數(shù)據(jù)庫中查詢數(shù)據(jù)辈末;
  • 查詢到數(shù)據(jù)后愚争,將數(shù)據(jù)寫入到緩存中,并且返回給用戶挤聘。

寫策略的步驟是:

  • 更新數(shù)據(jù)庫中的記錄轰枝;
  • 刪除緩存記錄。

你也許會問了组去,在寫策略中鞍陨,能否先刪除緩存,后更新數(shù)據(jù)庫呢从隆?答案是不行的诚撵,因為這樣也有可能出現(xiàn)緩存數(shù)據(jù)不一致的問題缭裆,我以用戶表的場景為例解釋一下。

假設(shè)某個用戶的年齡是 20寿烟,請求 A 要更新用戶年齡為 21澈驼,所以它會刪除緩存中的內(nèi)容。這時筛武,另一個請求 B 要讀取這個用戶的年齡缝其,它查詢緩存發(fā)現(xiàn)未命中后,會從數(shù)據(jù)庫中讀取到年齡為 20畅铭,并且寫入到緩存中氏淑,然后請求 A 繼續(xù)更改數(shù)據(jù)庫,將用戶的年齡更新為 21硕噩,這就造成了緩存和數(shù)據(jù)庫的不一致假残。


image.png

那么像 Cache Aside 策略這樣先更新數(shù)據(jù)庫,后刪除緩存就沒有問題了嗎炉擅?其實在理論上還是有缺陷的辉懒。假如某個用戶數(shù)據(jù)在緩存中不存在,請求 A 讀取數(shù)據(jù)時從數(shù)據(jù)庫中查詢到年齡為 20谍失,在未寫入緩存中時另一個請求 B 更新數(shù)據(jù)眶俩。它更新數(shù)據(jù)庫中的年齡為 21,并且清空緩存快鱼。這時請求 A 把從數(shù)據(jù)庫中讀到的年齡為 20 的數(shù)據(jù)寫入到緩存中颠印,造成緩存和數(shù)據(jù)庫數(shù)據(jù)不一致。


image.png

不過這種問題出現(xiàn)的幾率并不高抹竹,原因是緩存的寫入通常遠遠快于數(shù)據(jù)庫的寫入线罕,所以在實際中很難出現(xiàn)請求 B 已經(jīng)更新了數(shù)據(jù)庫并且清空了緩存,請求 A 才更新完緩存的情況窃判。而一旦請求 A 早于請求 B 清空緩存之前更新了緩存钞楼,那么接下來的請求就會因為緩存為空而從數(shù)據(jù)庫中重新加載數(shù)據(jù),所以不會出現(xiàn)這種不一致的情況袄琳。

Cache Aside 策略是我們?nèi)粘i_發(fā)中最經(jīng)常使用的緩存策略询件,不過我們在使用時也要學(xué)會依情況而變。比如說當(dāng)新注冊一個用戶唆樊,按照這個更新策略宛琅,你要寫數(shù)據(jù)庫,然后清理緩存(當(dāng)然緩存中沒有數(shù)據(jù)給你清理)窗轩『煌海可當(dāng)我注冊用戶后立即讀取用戶信息,并且數(shù)據(jù)庫主從分離時,會出現(xiàn)因為主從延遲所以讀不到用戶信息的情況仓洼。

而解決這個問題的辦法恰恰是在插入新數(shù)據(jù)到數(shù)據(jù)庫之后寫入緩存介陶,這樣后續(xù)的讀請求就會從緩存中讀到數(shù)據(jù)了。并且因為是新注冊的用戶色建,所以不會出現(xiàn)并發(fā)更新用戶信息的情況哺呜。

Cache Aside 存在的最大的問題是當(dāng)寫入比較頻繁時,緩存中的數(shù)據(jù)會被頻繁地清理箕戳,這樣會對緩存的命中率有一些影響某残。如果你的業(yè)務(wù)對緩存命中率有嚴格的要求,那么可以考慮兩種解決方案:

  1. 一種做法是在更新數(shù)據(jù)時也更新緩存陵吸,只是在更新緩存前先加一個分布式鎖玻墅,因為這樣在同一時間只允許一個線程更新緩存,就不會產(chǎn)生并發(fā)問題了壮虫。當(dāng)然這么做對于寫入的性能會有一些影響澳厢;
  2. 另一種做法同樣也是在更新數(shù)據(jù)時更新緩存,只是給緩存加一個較短的過期時間囚似,這樣即使出現(xiàn)緩存不一致的情況剩拢,緩存的數(shù)據(jù)也會很快地過期,對業(yè)務(wù)的影響也是可以接受饶唤。

當(dāng)然了徐伐,除了這個策略,在計算機領(lǐng)域還有其他幾種經(jīng)典的緩存策略募狂,它們也有各自適用的使用場景办素。

Read/Write Through(讀穿 / 寫穿)策略

這個策略的核心原則是用戶只與緩存打交道,由緩存和數(shù)據(jù)庫通信祸穷,寫入或者讀取數(shù)據(jù)摸屠。這就好比你在匯報工作的時候只對你的直接上級匯報,再由你的直接上級匯報給他的上級粱哼,你是不能越級匯報的。

Write Through 的策略是這樣的:先查詢要寫入的數(shù)據(jù)在緩存中是否已經(jīng)存在檩咱,如果已經(jīng)存在揭措,則更新緩存中的數(shù)據(jù),并且由緩存組件同步更新到數(shù)據(jù)庫中刻蚯,如果緩存中數(shù)據(jù)不存在绊含,我們把這種情況叫做“Write Miss(寫失效)”。

一般來說炊汹,我們可以選擇兩種“Write Miss”方式:一個是“Write Allocate(按寫分配)”躬充,做法是寫入緩存相應(yīng)位置,再由緩存組件同步更新到數(shù)據(jù)庫中;另一個是“No-write allocate(不按寫分配)”充甚,做法是不寫入緩存中以政,而是直接更新到數(shù)據(jù)庫中。

在 Write Through 策略中伴找,我們一般選擇“No-write allocate”方式盈蛮,原因是無論采用哪種“Write Miss”方式,我們都需要同步將數(shù)據(jù)更新到數(shù)據(jù)庫中技矮,而“No-write allocate”方式相比“Write Allocate”還減少了一次緩存的寫入抖誉,能夠提升寫入的性能。

Read Through 策略就簡單一些衰倦,它的步驟是這樣的:先查詢緩存中數(shù)據(jù)是否存在袒炉,如果存在則直接返回,如果不存在樊零,則由緩存組件負責(zé)從數(shù)據(jù)庫中同步加載數(shù)據(jù)我磁。

下面是 Read Through/Write Through 策略的示意圖:


image.png

Read Through/Write Through 策略的特點是由緩存節(jié)點而非用戶來和數(shù)據(jù)庫打交道,在我們開發(fā)過程中相比 Cache Aside 策略要少見一些淹接,原因是我們經(jīng)常使用的分布式緩存組件十性,無論是 Memcached 還是 Redis 都不提供寫入數(shù)據(jù)庫,或者自動加載數(shù)據(jù)庫中的數(shù)據(jù)的功能塑悼。而我們在使用本地緩存的時候可以考慮使用這種策略劲适,比如說在上一節(jié)中提到的本地緩存 Guava Cache 中的 Loading Cache 就有 Read Through 策略的影子。

我們看到 Write Through 策略中寫數(shù)據(jù)庫是同步的厢蒜,這對于性能來說會有比較大的影響霞势,因為相比于寫緩存,同步寫數(shù)據(jù)庫的延遲就要高很多了斑鸦。那么我們可否異步地更新數(shù)據(jù)庫愕贡?這就是我們接下來要提到的“Write Back”策略。

Write Back(寫回)策略

這個策略的核心思想是在寫入數(shù)據(jù)時只寫入緩存巷屿,并且把緩存塊兒標記為“臟”的固以。而臟塊兒只有被再次使用時才會將其中的數(shù)據(jù)寫入到后端存儲中。

需要注意的是嘱巾,在“Write Miss”的情況下憨琳,我們采用的是“Write Allocate”的方式,也就是在寫入后端存儲的同時要寫入緩存旬昭,這樣我們在之后的寫請求中都只需要更新緩存即可篙螟,而無需更新后端存儲了,我將 Write back 策略的示意圖放在了下面:

image.png

如果使用 Write Back 策略的話问拘,讀的策略也有一些變化了遍略。我們在讀取緩存時如果發(fā)現(xiàn)緩存命中則直接返回緩存數(shù)據(jù)惧所。如果緩存不命中則尋找一個可用的緩存塊兒,如果這個緩存塊兒是“臟”的绪杏,就把緩存塊兒中之前的數(shù)據(jù)寫入到后端存儲中下愈,并且從后端存儲加載數(shù)據(jù)到緩存塊兒,如果不是臟的寞忿,則由緩存組件將后端存儲中的數(shù)據(jù)加載到緩存中驰唬,最后我們將緩存設(shè)置為不是臟的,返回數(shù)據(jù)就好了腔彰。
image.png

發(fā)現(xiàn)了嗎叫编?其實這種策略不能被應(yīng)用到我們常用的數(shù)據(jù)庫和緩存的場景中,它是計算機體系結(jié)構(gòu)中的設(shè)計霹抛,比如我們在向磁盤中寫數(shù)據(jù)時采用的就是這種策略搓逾。無論是操作系統(tǒng)層面的 Page Cache,還是日志的異步刷盤杯拐,亦或是消息隊列中消息的異步寫入磁盤霞篡,大多采用了這種策略。因為這個策略在性能上的優(yōu)勢毋庸置疑端逼,它避免了直接寫磁盤造成的隨機寫問題朗兵,畢竟寫內(nèi)存和寫磁盤的隨機 I/O 的延遲相差了幾個數(shù)量級呢。

但因為緩存一般使用內(nèi)存顶滩,而內(nèi)存是非持久化的余掖,所以一旦緩存機器掉電,就會造成原本緩存中的臟塊兒數(shù)據(jù)丟失礁鲁。所以你會發(fā)現(xiàn)系統(tǒng)在掉電之后盐欺,之前寫入的文件會有部分丟失,就是因為 Page Cache 還沒有來得及刷盤造成的仅醇。

當(dāng)然冗美,你依然可以在一些場景下使用這個策略,在使用時析二,我想給你的落地建議是:你在向低速設(shè)備寫入數(shù)據(jù)的時候粉洼,可以在內(nèi)存里先暫存一段時間的數(shù)據(jù),甚至做一些統(tǒng)計匯總叶摄,然后定時地刷新到低速設(shè)備上漆改。比如說,你在統(tǒng)計你的接口響應(yīng)時間的時候准谚,需要將每次請求的響應(yīng)時間打印到日志中,然后監(jiān)控系統(tǒng)收集日志后再做統(tǒng)計去扣。但是如果每次請求都打印日志無疑會增加磁盤 I/O柱衔,那么不如把一段時間的響應(yīng)時間暫存起來樊破,經(jīng)過簡單的統(tǒng)計平均耗時,每個耗時區(qū)間的請求數(shù)量等等唆铐,然后定時地哲戚,批量地打印到日志中。

課程小結(jié)

本節(jié)課艾岂,我主要帶你了解了緩存使用的幾種策略顺少,以及每種策略適用的使用場景是怎樣的。我想讓你掌握的重點是:

1.Cache Aside 是我們在使用分布式緩存時最常用的策略王浴,你可以在實際工作中直接拿來使用脆炎。

2.Read/Write Through 和 Write Back 策略需要緩存組件的支持,所以比較適合你在實現(xiàn)本地緩存組件的時候使用氓辣;

3.Write Back 策略是計算機體系結(jié)構(gòu)中的策略秒裕,不過寫入策略中的只寫緩存,異步寫入后端存儲的策略倒是有很多的應(yīng)用場景钞啸。

而且几蜻,你還需要了解,我們今天提到的策略都是標準的使用姿勢体斩,在實際開發(fā)過程中需要結(jié)合實際的業(yè)務(wù)特點靈活使用甚至加以改造梭稚。這些業(yè)務(wù)特點包括但不僅限于:整體的數(shù)據(jù)量級情況,訪問的讀寫比例的情況絮吵,對于數(shù)據(jù)的不一致時間的容忍度弧烤,對于緩存命中率的要求等等。理論結(jié)合實踐源武,具體情況具體分析扼褪,你才能得到更好的解決方案。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粱栖,一起剝皮案震驚了整個濱河市话浇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌闹究,老刑警劉巖幔崖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異渣淤,居然都是意外死亡赏寇,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門价认,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嗅定,“玉大人,你說我怎么就攤上這事用踩∏耍” “怎么了忙迁?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碎乃。 經(jīng)常有香客問我姊扔,道長,這世上最難降的妖魔是什么梅誓? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任恰梢,我火速辦了婚禮,結(jié)果婚禮上梗掰,老公的妹妹穿的比我還像新娘嵌言。我一直安慰自己,他們只是感情好愧怜,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布呀页。 她就那樣靜靜地躺著,像睡著了一般拥坛。 火紅的嫁衣襯著肌膚如雪蓬蝶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天猜惋,我揣著相機與錄音丸氛,去河邊找鬼。 笑死著摔,一個胖子當(dāng)著我的面吹牛缓窜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谍咆,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼禾锤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了摹察?” 一聲冷哼從身側(cè)響起恩掷,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎供嚎,沒想到半個月后黄娘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡克滴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年逼争,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劝赔。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡誓焦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出着帽,到底是詐尸還是另有隱情杂伟,我是刑警寧澤竿秆,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站稿壁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏歉备。R本人自食惡果不足惜傅是,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蕾羊。 院中可真熱鬧喧笔,春花似錦、人聲如沸龟再。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽利凑。三九已至浆劲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哀澈,已是汗流浹背牌借。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留割按,地道東北人膨报。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像适荣,于是被迫代替她去往敵國和親现柠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容