面試官:3 種緩存更新策略是怎樣的?

作者:小林coding

計算機八股文網站:https://xiaolincoding.com

大家好,我是小林。

今天跟大家聊聊,常見的緩存更新策略。

  • Cache Aside(旁路緩存)策略;
  • Read/Write Through(讀穿 / 寫穿)策略;
  • Write Back(寫回)策略;

實際開發中,Redis 和 MySQL 的更新策略用的是 Cache Aside,另外兩種策略主要應用在計算機系統裏。

Cache Aside(旁路緩存)策略

Cache Aside(旁路緩存)策略是最常用的,應用程序直接與「數據庫、緩存」交互,並負責對緩存的維護,該策略又可以細分爲「讀策略」和「寫策略」。

寫策略的步驟:

  • 先更新數據庫中的數據,再刪除緩存中的數據。

讀策略的步驟:

  • 如果讀取的數據命中了緩存,則直接返回數據;
  • 如果讀取的數據沒有命中緩存,則從數據庫中讀取數據,然後將數據寫入到緩存,並且返回給用戶。

注意,寫策略的步驟的順序順序不能倒過來,即不能先刪除緩存再更新數據庫,原因是在「讀+寫」併發的時候,會出現緩存和數據庫的數據不一致性的問題。

舉個例子,假設某個用戶的年齡是 20,請求 A 要更新用戶年齡爲 21,所以它會刪除緩存中的內容。這時,另一個請求 B 要讀取這個用戶的年齡,它查詢緩存發現未命中後,會從數據庫中讀取到年齡爲 20,並且寫入到緩存中,然後請求 A 繼續更改數據庫,將用戶的年齡更新爲 21。

最終,該用戶年齡在緩存中是 20(舊值),在數據庫中是 21(新值),緩存和數據庫的數據不一致。

爲什麼「先更新數據庫再刪除緩存」不會有數據不一致的問題?

繼續用「讀 + 寫」請求的併發的場景來分析。

假如某個用戶數據在緩存中不存在,請求 A 讀取數據時從數據庫中查詢到年齡爲 20,在未寫入緩存中時另一個請求 B 更新數據。它更新數據庫中的年齡爲 21,並且清空緩存。這時請求 A 把從數據庫中讀到的年齡爲 20 的數據寫入到緩存中。

最終,該用戶年齡在緩存中是 20(舊值),在數據庫中是 21(新值),緩存和數據庫數據不一致。 從上面的理論上分析,先更新數據庫,再刪除緩存也是會出現數據不一致性的問題,但是在實際中,這個問題出現的概率並不高

因爲緩存的寫入通常要遠遠快於數據庫的寫入,所以在實際中很難出現請求 B 已經更新了數據庫並且刪除了緩存,請求 A 才更新完緩存的情況。而一旦請求 A 早於請求 B 刪除緩存之前更新了緩存,那麼接下來的請求就會因爲緩存不命中而從數據庫中重新讀取數據,所以不會出現這種不一致的情況。

Cache Aside 策略適合讀多寫少的場景,不適合寫多的場景,因爲當寫入比較頻繁時,緩存中的數據會被頻繁地清理,這樣會對緩存的命中率有一些影響。如果業務對緩存命中率有嚴格的要求,那麼可以考慮兩種解決方案:

  • 一種做法是在更新數據時也更新緩存,只是在更新緩存前先加一個分佈式鎖,因爲這樣在同一時間只允許一個線程更新緩存,就不會產生併發問題了。當然這麼做對於寫入的性能會有一些影響;
  • 另一種做法同樣也是在更新數據時更新緩存,只是給緩存加一個較短的過期時間,這樣即使出現緩存不一致的情況,緩存的數據也會很快過期,對業務的影響也是可以接受。

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

Read/Write Through(讀穿 / 寫穿)策略原則是應用程序只和緩存交互,不再和數據庫交互,而是由緩存和數據庫交互,相當於更新數據庫的操作由緩存自己代理了。

Read Through 策略

先查詢緩存中數據是否存在,如果存在則直接返回,如果不存在,則由緩存組件負責從數據庫查詢數據,並將結果寫入到緩存組件,最後緩存組件將數據返回給應用。

Write Through 策略

當有數據更新的時候,先查詢要寫入的數據在緩存中是否已經存在:

  • 如果緩存中數據已經存在,則更新緩存中的數據,並且由緩存組件同步更新到數據庫中,然後緩存組件告知應用程序更新完成。
  • 如果緩存中數據不存在,直接更新數據庫,然後返回;

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

Read Through/Write Through 策略的特點是由緩存節點而非應用程序來和數據庫打交道,在我們開發過程中相比 Cache Aside 策略要少見一些,原因是我們經常使用的分佈式緩存組件,無論是 Memcached 還是 Redis 都不提供寫入數據庫和自動加載數據庫中的數據的功能。而我們在使用本地緩存的時候可以考慮使用這種策略。

Write Back(寫回)策略

Write Back(寫回)策略在更新數據的時候,只更新緩存,同時將緩存數據設置爲髒的,然後立馬返回,並不會更新數據庫。對於數據庫的更新,會通過批量異步更新的方式進行。

實際上,Write Back(寫回)策略也不能應用到我們常用的數據庫和緩存的場景中,因爲 Redis 並沒有異步更新數據庫的功能。

Write Back 是計算機體系結構中的設計,比如 CPU 的緩存、操作系統中文件系統的緩存都採用了 Write Back(寫回)策略。

Write Back 策略特別適合寫多的場景,因爲發生寫操作的時候, 只需要更新緩存,就立馬返回了。比如,寫文件的時候,實際上是寫入到文件系統的緩存就返回了,並不會寫磁盤。

但是帶來的問題是,數據不是強一致性的,而且會有數據丟失的風險,因爲緩存一般使用內存,而內存是非持久化的,所以一旦緩存機器掉電,就會造成原本緩存中的髒數據丟失。所以你會發現系統在掉電之後,之前寫入的文件會有部分丟失,就是因爲 Page Cache 還沒有來得及刷盤造成的。

這裏貼一張 CPU 緩存與內存使用 Write Back 策略的流程圖:

有沒有覺得這個流程很熟悉?因爲我在寫 CPU 緩存文章的時候提到過。

系列《圖解Redis》文章

面試篇:

數據類型篇:

持久化篇:

功能篇:

高可用篇:

緩存篇:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章