快取策略 | 描述 | 優點 | 缺點 | 適用場景 |
---|---|---|---|---|
旁路快取 (Cache Aside Pattern) | 服務端需要同時維護資料庫和快取,以資料庫的結果為準。 讀請求先查快取,沒有命中則查資料庫並更新快取。寫請求先更新資料庫,然後刪除快取。 |
適合讀多寫少的場景,快取命中率高 | 寫操作複雜,存在短暫的資料不一致風險 | 資料讀寫比例懸殊、讀操作頻繁的場景。 |
讀寫穿透 (Read/Write Through Pattern) | 服務端將快取視為主要資料儲存,所有資料的讀寫都透過快取,快取服務負責將資料同步到資料庫 | 簡化了應用程式的職責,資料一致性高。 | 實現複雜,增加了快取服務的壓力。 | 資料一致性要求高,且讀寫操作頻繁的場景。 |
非同步快取寫入 (Write Behind Pattern) | 與讀寫穿透類似,但寫操作不立即同步到資料庫,而是非同步批次更新資料庫 | 寫操作效率高,減輕了資料庫壓力。 | 存在資料丟失風險,資料一致性較低。 | 寫操作頻繁且對實時一致性要求不高的場景 |
1 旁路快取
Cache Aside Pattern(旁路快取)適合讀請求比較多的場景
Cache Aside Pattern 中服務端需要同時維繫 db 和 cache,並且是以 db 的結果為準。
1.1.1 寫
- 先更新db
- 直接刪除快取
1.1.2 讀
- 先讀快取
- 有,則從快取返回。
- 沒有,從db中讀取返回。
- 再將讀取的資料寫入快取
1.1.3 常見問題
1.寫,寫DB後咋是刪快取不是更新快取?
寫N次,只有最後一次資料的有效,前面寫的都會被覆蓋。
讀不到的時候自然會重建快取,我們直接刪除了,等讀的時候重建即可。
2.寫,可不可以先刪除快取,再更新DB?
不可以。
前面已經講了,我們更新的時候,db更新沒問題,但是快取呢不做更新,直接刪除。
現在的問題就是,更新db和刪除快取這兩個操作的先後問題。
假設我們資料庫中有個值name=yang
,我們準備更新成yang37
。
2.1 (×)先刪除快取,再更新db。
這個情況,主要是考慮併發場景,在刪除快取 -> 更新db期間,db中的始終是舊資料。
如果執行緒2發生了讀取請求,會導致資料不一致。
- 執行緒1:嘗試更新name從yang到yang37
- 執行緒2:嘗試讀取name的值
最終我們可以看到,快取中的資料還是舊資料yang,而db中是正確的yang37,資料不一致。
問題的根源在哪裡,在於你執行緒2讀取快取是很快的,快取中沒值,必然觸發快取的重建。
- 執行緒1的db更新完了,那就皆大歡喜,即左邊執行緒1整個期間沒有併發問題。
- 執行緒1的db沒更新完,db中的必定是舊資料,重建的快取值也必定成舊資料,導致出問題。
注意圖最下面的兩個框框。最重要的是,在快取有效期內,你快取的一直是髒資料,如果這個快取沒過期時間,那麼快取中的資料始終有問題。
這個問題出現的機率大嗎?即左邊刪除快取 -> 更新db的期間能不能包裹住右邊邊。
實際上,db查詢資料比更新資料快,這個情況很容易出現。
2.2 (√) 先更新db,再刪除快取。
接著上面的,來看下先更新db的。
嗯,這裡,如果更新db -> 刪除快取期間有讀取請求,我們的執行緒2還是會讀取到髒資料。
但是,咱們最終快取中的資料不存在,下次查詢會觸發重建。
先刪除快取再更新db的方式,邏輯上就存在問題了,咱們這個影響程度小的多。
咱們只是在這期間有短暫的不一致問題,不會導致最終狀態下的資料不一致。
但是,還是可能有快取最終不一致的情況,就是剛好快取失效了,例如下圖。
這裡的問題點是什麼,執行緒2的更新快取操作覆蓋了執行緒1的更新db+刪除快取
的操作。
它必須要完整的包裹住執行緒1的更新操作和刪除操作。
- 如果執行緒1更新操作線上程2查詢db的操作之前,那麼此時查詢到的資料已經被更新了,重建的快取值剛好是正確的。
- 如果執行緒1的刪除操作發生線上程2的更新快取之後,那麼此時快取中的資料已經被刪除了,下次重建會成為正確的值,也符合我們的預期。
我們注意下哈,先決條件是執行緒2從db查到舊資料之後,執行緒1開始更新db了。
然後,必須完完整整的包裹住這整個操作,必須得下面這樣。
即,哪怕我像下面這樣兩種情況,它也不會有問題,必須滿足上面包裹住的場景,才可能有問題。
實際上,db查詢資料比更新資料快,所以這個更新db+刪除快取的時間
很難比查詢db+更新快取的時間長
。
別忘了我們還有併發+快取失效的背景,這所有因素組合在一起才可能有這個資料不一致的情況,所以出現問題的機率是很低的。
你說,咱們方案1中過期時間的場景都還沒討論呢。哈哈,其實不用討論了,你想啊,方案1還沒說過期就有很大隱患了,還不要說咱們現在這裡的討論的方案2+過期的場景。
綜上呢,咱們的正確方案是:先更新db,再刪除快取。
1.2 讀寫穿透
Read/Write Through Pattern 中服務端把 cache 視為主要資料儲存,從中讀取資料並將資料寫入其中。
cache 服務負責將此資料讀取和寫入 db,從而減輕了應用程式的職責。
1.3 非同步快取寫入
Write Behind Pattern 和 Read/Write Through Pattern 很相似,兩者都是由 cache 服務來負責 cache 和 db 的讀寫。
讀寫穿透是同步更新 cache 和 db,而非同步快取寫入則是隻更新快取,不直接更新 db,而是改為非同步批次的方式來更新 db。