Cache-aside 將快取邏輯放在應用程式中。Read-through 和 write-through 則將它移到快取層本身,因此應用程式將快取視為資料庫來存取,而快取負責從後端儲存庫載入與寫入。差異看似微小,卻改變了誰擁有邏輯、快取的一致性,以及複雜度所在之處。了解這三種模式能讓您根據每個使用情境選擇正確的一致性與複雜度權衡。
這是 Redis Masterclass 的第 10 部分,接續 cache-aside。
Read-through:快取在未命中時載入
在 read-through 中,應用程式總是從快取讀取,而快取負責在未命中時從資料庫擷取。在 cache-aside 中,應用程式執行「從資料庫載入並填入快取」的步驟,而 read-through 將此步驟隱藏在快取層後方:
// application code is simple: just read from the cache
const user = await cache.get(`user:${id}`);
// the cache layer, on a miss, loads from the DB, stores it, and returns it
Enter fullscreen mode Exit fullscreen mode
從外部看,行為與 cache-aside 幾乎相同:延遲載入、TTL、未命中時填入快取。差異在於邏輯所在之處。Read-through 需要一個可以呼叫資料庫的快取(供應者、函式庫,或您自行建置的包裝器),因此載入邏輯集中在一處,而不是在每個呼叫位置重複。
優點是實作的一致性:每次讀取都經過相同的載入路徑,因此不會忘記在某個程式碼路徑填入快取。代價是需要有載入器的快取層。在實務中,許多團隊透過在單一共用函式中包裝 cache-aside 來獲得 read-through 的好處,這與此概念相同,但不需要特殊基礎架構。
Write-through:快取寫入資料庫
Write-through 與 read-through 搭配用於寫入端。應用程式寫入快取,而快取在確認前同步寫入資料庫:
// application writes to the cache; the cache writes through to the DB
await cache.set(`user:${id}`, updatedUser);
// internally: cache updates Redis AND writes to the database, then returns
Enter fullscreen mode Exit fullscreen mode
快取與資料庫在單一操作中同時更新,因此快取與資料庫總是保持一致(不會因為忘記失效而產生陳舊的項目)。每次寫入都讓它們保持同步。
權衡在於寫入延遲。因為寫入會同步寫入快取與資料庫,所以寫入速度與資料庫寫入加上快取寫入一樣慢。您已將快取加入寫入路徑,因此寫入不會變快(讀取會)。而且無論資料是否會被讀取,您都會在寫入時快取資料,這可能會讓快取填滿冷資料。
Read-through 加上 write-through:一致但寫入不會更快
一起使用時,read-through 和 write-through 提供一個會自動與資料庫保持一致的快取:讀取在未命中時填入,寫入更新兩個儲存庫。應用程式將快取視為其資料介面,而快取會保持自我同步。當您希望在不分散應用程式中的失效邏輯的情況下獲得強快取一致性時,這很有吸引力。
誠實的限制:寫入不會更快(它們會命中兩個儲存庫),快取會保存可能永遠不會被讀取的資料(每次寫入時都寫入),而且您需要快取層支援此模式。它適合用於一致性重要且希望將快取邏輯集中的讀取密集型資料,對於簡單快取而言,cache-aside 的寫入時刪除已足夠,這是多餘的。
在模式之間選擇
以下是到目前為止三種模式的比較:
- Cache-aside:應用程式管理快取;未命中時載入,寫入時失效。最簡單、最常見、可優雅降級,但邏輯分散在應用程式中,且可能應用不一致。
- Read-through:快取在未命中時載入,集中讀取邏輯。行為與 cache-aside 相同,如果您有支援它的快取層,則更乾淨。
- Write-through:快取同步寫入資料庫,讓快取與資料庫保持一致,代價是寫入延遲與快取未讀取的資料。
一個常見的實用設定是 cache-aside 讀取,搭配 write-through 風格的一致性,透過在單一經過良好測試的共用函式中更新資料庫並失效快取來達成,在沒有特殊快取提供者的情況下獲得大部分好處。
一致性來自何處
這些模式貫穿的主題是快取一致性在何處被強制執行。Cache-aside 依賴應用程式記得失效。Write-through 則在結構上強制執行,因為每次寫入都會更新兩個儲存庫。如果忘記失效導致快取陳舊的錯誤,移至 write-through 風格的模式(或集中失效)可讓一致性自動化,而非依賴開發人員記得。
Read-through 和 write-through 以應用程式的控制權換取快取的自動一致性。當您想要集中且不易遺忘的快取邏輯,並且可以容忍 write-through 的延遲時,它們是正確的選擇。當您想要簡單性和優雅降級時,cache-aside 仍然是主力。接下來的模式更進一步:write-behind 以寫入速度換取一致性,而 refresh-ahead 則以工作量換取完全避免未命中。
重點摘要
- Read-through 將未命中時載入的邏輯移至快取層,使其集中化,因此沒有任何程式碼路徑會忘記填入快取。
- Write-through 讓快取同步寫入資料庫,自動保持快取與資料庫一致。
- Write-through 不會加快寫入速度(它們會命中兩個儲存庫),且無論資料是否會被讀取,都會在寫入時快取資料。
- Read-through 和 write-through 一起提供自動快取一致性,代價是寫入延遲與支援的快取層。
- 一個實用的替代方案是 cache-aside 讀取,加上共用更新資料庫與失效函式,在沒有特殊基礎架構的情況下獲得一致性。
常見問題
Cache-aside 與 read-through 的差異是什麼?
在 cache-aside 中,應用程式在未命中時從資料庫載入並填入快取。在 read-through 中,快取層自行執行該載入,因此應用程式只需從快取讀取。行為相似,但 read-through 將邏輯集中化。
什麼是 write-through 快取?
一種模式,其中應用程式寫入快取,而快取在返回前同步寫入資料庫。兩個儲存庫會一起更新,以寫入延遲增加為代價,讓快取與資料庫保持一致。
Write-through 會讓寫入變快嗎?
不會。寫入會同步寫入快取與資料庫,因此至少與資料庫寫入一樣慢。Write-through 改善讀取一致性,而非寫入速度;讀取受益是因為快取總是被填入且保持最新。
我什麼時候應該使用 read-through 和 write-through 而非 cache-aside?
當您希望快取一致性被自動強制執行,且快取邏輯集中而非分散在應用程式中,且可以容忍額外的寫入延遲時。對於具有優雅降級的簡單快取而言,cache-aside 通常已足夠。
如何在沒有 write-through 的情況下保持快取一致?
使用 cache-aside,但將寫入集中到單一共用函式中,該函式會一起更新資料庫與失效快取。這讓失效不易被遺忘,近似 write-through 的一致性,而無需特殊的快取層。
延伸閱讀
This article was originally published on amanksingh.com/blog/redis-read-through-write-through.
關於作者
Aman Kumar Singh 是印度諾伊達的團隊主管兼資深軟體工程師,撰寫關於使用 TypeScript、Next.js、NestJS、PostgreSQL 和 Redis 的全端工程、系統設計和生產 SaaS 的文章。
- 作品集:amanksingh.com
- GitHub:amansingh1501
- LinkedIn:amansingh1597
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.