實務上快取有多種模式(patterns),每 1 種都有適合的應用場景,大多數後端工程師應該只對 Cache Aside 快取模式相對熟悉,較少接觸其他種模式,不過實際面試時也可能被詢問到 Cache Aside 以外的模式,原因在於 Cache Aside 模式沒辦法在所有的應用場景都完美運作,因此需要對常見的 Cache 模式有基本認識,才有辦法提出合理的解答。
本文將介紹 6 種快取模式。
Cache Aside 模式
Cache Aside 是後端工程師最常用使用的快取方式,它的運作如下圖所示:
應用(application)會先查詢 cache server 有沒有相關的快取,如果有的話,就拿出來直接回應給使用者;如果沒有快取,就對資料庫進行查詢,再將查詢結果放到 cache server, 如此一來,下次再有相同的 request 的話,就能夠使用快取。
如果你在程式碼中看到類似以下的模式,那就是使用 Cache Aside 模式:
def api(...):
...
# step 1
cache = get_cache(key)
if cache:
# step 2
return response.ok(cache)
# step 3, 4
data = query_database()
# step 5
set_cache(key, data, ttl)
return response.ok(data)
p.s. 上述程式碼的註解數字對應的是 Cache Aside 圖上的數字
Cache Aside 適合用在符合以下 2 種情況的資料:
- 快取不常變動
- 快取失效也不會造成資料庫負擔驟增
- 讀取遠多於寫入的情況
如果你的資料不符合上述條件,最好別使用 Cache Aside, 詳見後端工程師經驗談 — 有時候用 Cache Aside 是個壞主意 。
使用 Cache Aside 的缺點是初次建立快取或快取失效時會有較長的 latency, 因為它需要花費 1 次詢問 cache server 的 latency, 確認沒有 cache 存在才會向資料庫送出查詢,最後再將資料快取起來。
另外, Cache Aside 也會有資料庫內的資料不一致的情況,因為應用端只有在快取失效或不存在時,才會對資料庫進行查詢並建立快取,所以資料庫內的資料已經更新時, cache server 內的資料就可能還是舊的資料,需要等到快取失效或者主動更新快取才能保持資料一致。
Refresh-Ahead 模式
如果資料快取失效會造成資料庫負擔驟增的情況,或者每次產生快取會有較高的 latency 產生時,可以改採用 Refresh-Ahead 模式。
Refresh-Ahead 模式會在快取失效前,主動更新快取,所以應用端不需要判斷是否有快取資料存在,也不需要由應用端更新快取。
使用 Refresh-Ahead 模式的好處在於:
- 減少應用端的 latency
- 應用端的實作邏輯也相對單純,只要直接讀取快取即可
Refresh-Ahead 不適合用在快取大量異質資料的情況,例如使用 Refresh-Ahead 快取全站使用者資料就相當不適合,因為不是所有使用者都會上線,因此不僅浪費快取伺服器的資源,也容易拉長快取更新時間,甚至更新快取時對資料庫造成負擔。
Read Through 模式
Read Through 模式與 Cache Aside 有點類似,不過差別在於應用端(application)只需要跟 cache server 溝通,如果 cache server 中有快取存在,即可直接回覆應用端;如果 cache server 中沒有快取存在,則 cache server 會代為向資料庫查詢,並進行快取。
使用 Read Through 模式與 Cache Aside 模式一樣會有初次建立快取、快取失效時的 latency 問題。
同樣地, Read Through 模式也會 Cache Aside 模式一樣有與資料庫資料不一致的問題。
使用 Read Through 模式的優點在於應用端程式邏輯只需與 cache server 互動即可,不像 Cache Aside 還需要實作建立快取的邏輯。
Write Through 模式
Write Through 與 Read Through 模式行為相反,它是寫入資料時,直接寫入 cache server, 由 cache server 負責與資料庫更新,是一環扣一環的模式。
p.s. 你也可以想像 cache server 之後就是 1 個整體,我們只跟它互動,剩下的工作它會自己處理
RedisGears/rgsync 屬於這種,資料寫入 Redis 之後,它會再幫忙寫入資料庫。
使用 Write Through 模式的好處在於快取資料與資料庫之間有更高的一致性,只要資料庫有更新, cache server 內的資料必然會跟著更新,適合寫入之後立馬要頻繁進行讀取的情況。
缺點則是資料被不分青紅皂白一律快取起來,會造成 cache server 資源的浪費。
通常 Write Through 模式會與 Read Through 模式搭配,統一由 cache server 負責資料的讀取與寫入,應用端的實作邏輯也是相對單純,只要直接讀取快取即可。
Write Behind / Write Back 模式 / 非同步快取寫入模式
由於 Write Through 模式同時需要寫入 cache server 與資料庫,所以勢必會增加一些 latency, 因此 Write Behind / Write Back 模式進一步把寫入資料庫的部分改為非同步方式,藉此降低 latency 。
Write Behind / Write Back 模式相當適合寫入操作很頻繁的情況,非同步的寫入資料庫方式也可以藉由控制寫入速度等手段,降低資料庫負擔。
不過 Write Behind 的缺點在於有資料遺失(data loss)的可能性,例如 cache server 更新資料庫之前故障,就有可能造成最新的資料遺失。
Write Around 模式
Write Around 模式是單指資料只寫入資料庫,不寫入 cache server 的情況,例如下列程式碼:
def write_around(db, data):
db.write(data)
至於讀取資料之後要怎麼快取,就依照需求搭配,例如 Write Around 搭配 Read Throught:
Write Around 模式的優勢在於可以避免像 Write Through 那般不分青紅皂白都快取的情況。
總結
每 1 種快取模式都有各自的優缺點,沒有絕對完美的模式,只有適合的應用情境,因此理解各種模式與其適合的應用情境相當重要,如此才能套用合適的模式解決問題。
以上!
Enjoy!
References
RedisGears/rgsync: A Write-Behind and Write-Through recipe for RedisGears
Caching patterns - Database Caching Strategies Using Redis
Write-behind, write-through, and read-through caching | Redis Documentation Center
Overview of Caching, Distributed Cache, Caching Patterns & Techniques