面試官:3 種快取更新策略是怎樣的?

2022-07-19 15:01:24

作者:小林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》文章

面試篇:

資料型別篇:

持久化篇:

功能篇:

高可用篇:

快取篇: